From 32911bb071345c798b9789c6461fe7d21a46d309 Mon Sep 17 00:00:00 2001 From: Emmanuel Lodovice Date: Thu, 12 Oct 2023 17:56:29 -0700 Subject: [PATCH 1/3] Update alertmanager Signed-off-by: Emmanuel Lodovice --- go.mod | 13 +- go.sum | 28 +- .../alertobserver/alertobserver.go | 36 + .../alertmanager/alertobserver/testing.go | 46 + .../prometheus/alertmanager/api/api.go | 6 + .../prometheus/alertmanager/api/v1/api.go | 45 +- .../prometheus/alertmanager/api/v2/api.go | 35 +- .../prometheus/alertmanager/api/v2/testing.go | 48 + .../prometheus/alertmanager/config/config.go | 2 +- .../alertmanager/config/notifiers.go | 6 +- .../alertmanager/dispatch/dispatch.go | 32 +- .../prometheus/alertmanager/dispatch/route.go | 23 + .../featurecontrol/featurecontrol.go | 78 ++ .../prometheus/alertmanager/notify/notify.go | 228 +++-- .../alertmanager/notify/pushover/pushover.go | 6 + .../prometheus/alertmanager/ui/web.go | 2 +- .../prometheus/common/assets/LICENSE | 201 +++++ .../prometheus/common/assets/Makefile | 18 + .../prometheus/common/assets/embed_gzip.go | 123 +++ .../prometheus/common/assets/testdata/both | 1 + .../prometheus/common/assets/testdata/both.gz | Bin 0 -> 29 bytes .../common/assets/testdata/compressed.gz | Bin 0 -> 35 bytes .../common/assets/testdata/uncompressed | 1 + .../common/server/static_file_server.go | 53 ++ vendor/github.com/rs/cors/README.md | 23 +- vendor/github.com/rs/cors/cors.go | 203 +++-- vendor/github.com/rs/cors/utils.go | 95 +- vendor/golang.org/x/net/http2/Dockerfile | 51 -- vendor/golang.org/x/net/http2/Makefile | 3 - vendor/golang.org/x/net/http2/server.go | 8 - vendor/golang.org/x/net/http2/transport.go | 3 +- .../x/net/publicsuffix/data/children | Bin 2876 -> 2976 bytes .../golang.org/x/net/publicsuffix/data/nodes | Bin 48280 -> 46610 bytes .../golang.org/x/net/publicsuffix/data/text | 2 +- vendor/golang.org/x/net/publicsuffix/table.go | 14 +- vendor/golang.org/x/text/unicode/norm/trie.go | 2 +- .../tools/go/internal/packagesdriver/sizes.go | 11 +- .../golang.org/x/tools/go/packages/golist.go | 9 +- .../x/tools/go/packages/packages.go | 13 +- .../x/tools/go/types/objectpath/objectpath.go | 827 ++++++++++++++++++ .../x/tools/internal/gcimporter/iexport.go | 142 ++- .../x/tools/internal/gcimporter/iimport.go | 85 +- .../x/tools/internal/gocommand/invoke.go | 4 +- .../x/tools/internal/typeparams/common.go | 6 + .../x/tools/internal/typeparams/coretype.go | 8 +- .../x/tools/internal/typeparams/termlist.go | 2 +- .../internal/typeparams/typeparams_go117.go | 2 +- .../internal/typeparams/typeparams_go118.go | 2 +- .../x/tools/internal/typeparams/typeterm.go | 9 +- .../internal/typesinternal/objectpath.go | 24 + vendor/modules.txt | 21 +- 51 files changed, 2203 insertions(+), 397 deletions(-) create mode 100644 vendor/github.com/prometheus/alertmanager/alertobserver/alertobserver.go create mode 100644 vendor/github.com/prometheus/alertmanager/alertobserver/testing.go create mode 100644 vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go create mode 100644 vendor/github.com/prometheus/common/assets/LICENSE create mode 100644 vendor/github.com/prometheus/common/assets/Makefile create mode 100644 vendor/github.com/prometheus/common/assets/embed_gzip.go create mode 100644 vendor/github.com/prometheus/common/assets/testdata/both create mode 100644 vendor/github.com/prometheus/common/assets/testdata/both.gz create mode 100644 vendor/github.com/prometheus/common/assets/testdata/compressed.gz create mode 100644 vendor/github.com/prometheus/common/assets/testdata/uncompressed create mode 100644 vendor/github.com/prometheus/common/server/static_file_server.go delete mode 100644 vendor/golang.org/x/net/http2/Dockerfile delete mode 100644 vendor/golang.org/x/net/http2/Makefile create mode 100644 vendor/golang.org/x/tools/go/types/objectpath/objectpath.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/objectpath.go diff --git a/go.mod b/go.mod index de39641a0b..4e76b6c0b1 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( go.opentelemetry.io/otel/sdk v1.16.0 go.opentelemetry.io/otel/trace v1.16.0 go.uber.org/atomic v1.11.0 - golang.org/x/net v0.14.0 + golang.org/x/net v0.15.0 golang.org/x/sync v0.3.0 golang.org/x/time v0.3.0 google.golang.org/grpc v1.56.2 @@ -182,11 +182,12 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/common/assets v0.2.0 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.10.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/redis/rueidis v1.0.14-go1.18 // indirect - github.com/rs/cors v1.9.0 // indirect + github.com/rs/cors v1.10.1 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sercand/kuberesolver v2.4.0+incompatible // indirect @@ -216,13 +217,13 @@ require ( go.uber.org/zap v1.21.0 // indirect go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.13.0 // indirect golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.12.0 // indirect - golang.org/x/tools v0.11.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.12.0 // indirect google.golang.org/api v0.132.0 // indirect @@ -263,3 +264,5 @@ replace github.com/google/gnostic => github.com/googleapis/gnostic v0.6.9 replace gopkg.in/alecthomas/kingpin.v2 => github.com/alecthomas/kingpin v1.3.8-0.20210301060133-17f40c25f497 replace github.com/sercand/kuberesolver => github.com/sercand/kuberesolver/v4 v4.0.0 + +replace github.com/prometheus/alertmanager => github.com/emanlodovice/prometheus-alertmanager v0.0.0-20231011203108-c700916131f5 diff --git a/go.sum b/go.sum index 6b8ca41367..0f574a31bd 100644 --- a/go.sum +++ b/go.sum @@ -569,6 +569,8 @@ github.com/efficientgo/core v1.0.0-rc.2/go.mod h1:FfGdkzWarkuzOlY04VY+bGfb1lWrja github.com/efficientgo/e2e v0.14.1-0.20230710114240-c316eb95ae5b h1:8VX23BNufsa4KCqnnEonvI3yrou2Pjp8JLcbdVn0Fs8= github.com/efficientgo/tools/extkingpin v0.0.0-20220817170617-6c25e3b627dd h1:VaYzzXeUbC5fVheskcKVNOyJMEYD+HgrJNzIAg/mRIM= github.com/efficientgo/tools/extkingpin v0.0.0-20220817170617-6c25e3b627dd/go.mod h1:ZV0utlglOczUWv3ih2AbqPSoLoFzdplUYxwV62eZi6Q= +github.com/emanlodovice/prometheus-alertmanager v0.0.0-20231011203108-c700916131f5 h1:pz5Vhj65QSK9mrfWSDhf8PVPC05sawwHOqSje5HAmmA= +github.com/emanlodovice/prometheus-alertmanager v0.0.0-20231011203108-c700916131f5/go.mod h1:o1s5N63DHIYv93zxC+jec5nefnJXbIAq4uot4YQxFZM= github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -1093,8 +1095,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prometheus/alertmanager v0.26.0 h1:uOMJWfIwJguc3NaM3appWNbbrh6G/OjvaHMk22aBBYc= -github.com/prometheus/alertmanager v0.26.0/go.mod h1:rVcnARltVjavgVaNnmevxK7kOn7IZavyf0KNgHkbEpU= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -1120,6 +1120,8 @@ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM= +github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/exporter-toolkit v0.8.2/go.mod h1:00shzmJL7KxcsabLWcONwpyNEuWhREOnFqZW7vadFS0= @@ -1144,8 +1146,8 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= -github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -1347,8 +1349,8 @@ golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1455,8 +1457,8 @@ golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1612,7 +1614,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1626,8 +1628,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1702,8 +1704,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/prometheus/alertmanager/alertobserver/alertobserver.go b/vendor/github.com/prometheus/alertmanager/alertobserver/alertobserver.go new file mode 100644 index 0000000000..cf05d9210c --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/alertobserver/alertobserver.go @@ -0,0 +1,36 @@ +// Copyright 2023 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package alertobserver + +import ( + "github.com/prometheus/alertmanager/types" +) + +const ( + EventAlertReceived string = "received" + EventAlertRejected string = "rejected" + EventAlertAddedToAggrGroup string = "addedAggrGroup" + EventAlertFailedAddToAggrGroup string = "failedAddAggrGroup" + EventAlertPipelineStart string = "pipelineStart" + EventAlertPipelinePassStage string = "pipelinePassStage" + EventAlertMuted string = "muted" + EventAlertSent string = "sent" + EventAlertSendFailed string = "sendFailed" +) + +type AlertEventMeta map[string]interface{} + +type LifeCycleObserver interface { + Observe(event string, alerts []*types.Alert, meta AlertEventMeta) +} diff --git a/vendor/github.com/prometheus/alertmanager/alertobserver/testing.go b/vendor/github.com/prometheus/alertmanager/alertobserver/testing.go new file mode 100644 index 0000000000..66f774fbb7 --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/alertobserver/testing.go @@ -0,0 +1,46 @@ +// Copyright 2023 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package alertobserver + +import ( + "sync" + + "github.com/prometheus/alertmanager/types" +) + +type FakeLifeCycleObserver struct { + AlertsPerEvent map[string][]*types.Alert + PipelineStageAlerts map[string][]*types.Alert + MetaPerEvent map[string][]AlertEventMeta + Mtx sync.RWMutex +} + +func (o *FakeLifeCycleObserver) Observe(event string, alerts []*types.Alert, meta AlertEventMeta) { + o.Mtx.Lock() + defer o.Mtx.Unlock() + if event == EventAlertPipelinePassStage { + o.PipelineStageAlerts[meta["stageName"].(string)] = append(o.PipelineStageAlerts[meta["stageName"].(string)], alerts...) + } else { + o.AlertsPerEvent[event] = append(o.AlertsPerEvent[event], alerts...) + } + o.MetaPerEvent[event] = append(o.MetaPerEvent[event], meta) +} + +func NewFakeLifeCycleObserver() *FakeLifeCycleObserver { + return &FakeLifeCycleObserver{ + PipelineStageAlerts: map[string][]*types.Alert{}, + AlertsPerEvent: map[string][]*types.Alert{}, + MetaPerEvent: map[string][]AlertEventMeta{}, + } +} diff --git a/vendor/github.com/prometheus/alertmanager/api/api.go b/vendor/github.com/prometheus/alertmanager/api/api.go index 59bfb76da6..db4b3b5337 100644 --- a/vendor/github.com/prometheus/alertmanager/api/api.go +++ b/vendor/github.com/prometheus/alertmanager/api/api.go @@ -25,6 +25,7 @@ import ( "github.com/prometheus/common/model" "github.com/prometheus/common/route" + "github.com/prometheus/alertmanager/alertobserver" apiv1 "github.com/prometheus/alertmanager/api/v1" apiv2 "github.com/prometheus/alertmanager/api/v2" "github.com/prometheus/alertmanager/cluster" @@ -74,6 +75,9 @@ type Options struct { // according to the current active configuration. Alerts returned are // filtered by the arguments provided to the function. GroupFunc func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) + // AlertLCObserver is used to add hooks to the different alert life cycle events. + // If nil then no observer methods will be invoked in the life cycle events. + AlertLCObserver alertobserver.LifeCycleObserver } func (o Options) validate() error { @@ -117,6 +121,7 @@ func New(opts Options) (*API, error) { opts.Peer, log.With(l, "version", "v1"), opts.Registry, + opts.AlertLCObserver, ) v2, err := apiv2.NewAPI( @@ -127,6 +132,7 @@ func New(opts Options) (*API, error) { opts.Peer, log.With(l, "version", "v2"), opts.Registry, + opts.AlertLCObserver, ) if err != nil { return nil, err diff --git a/vendor/github.com/prometheus/alertmanager/api/v1/api.go b/vendor/github.com/prometheus/alertmanager/api/v1/api.go index 39018a7589..d8b4ce0a19 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v1/api.go +++ b/vendor/github.com/prometheus/alertmanager/api/v1/api.go @@ -30,6 +30,7 @@ import ( "github.com/prometheus/common/route" "github.com/prometheus/common/version" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/api/metrics" "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/config" @@ -67,14 +68,15 @@ func setCORS(w http.ResponseWriter) { // API provides registration of handlers for API routes. type API struct { - alerts provider.Alerts - silences *silence.Silences - config *config.Config - route *dispatch.Route - uptime time.Time - peer cluster.ClusterPeer - logger log.Logger - m *metrics.Alerts + alerts provider.Alerts + silences *silence.Silences + config *config.Config + route *dispatch.Route + uptime time.Time + peer cluster.ClusterPeer + logger log.Logger + m *metrics.Alerts + alertLCObserver alertobserver.LifeCycleObserver getAlertStatus getAlertStatusFn @@ -91,19 +93,21 @@ func New( peer cluster.ClusterPeer, l log.Logger, r prometheus.Registerer, + o alertobserver.LifeCycleObserver, ) *API { if l == nil { l = log.NewNopLogger() } return &API{ - alerts: alerts, - silences: silences, - getAlertStatus: sf, - uptime: time.Now(), - peer: peer, - logger: l, - m: metrics.NewAlerts("v1", r), + alerts: alerts, + silences: silences, + getAlertStatus: sf, + uptime: time.Now(), + peer: peer, + logger: l, + m: metrics.NewAlerts("v1", r), + alertLCObserver: o, } } @@ -447,6 +451,10 @@ func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...* if err := a.Validate(); err != nil { validationErrs.Add(err) api.m.Invalid().Inc() + if api.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{"msg": err.Error()} + api.alertLCObserver.Observe(alertobserver.EventAlertRejected, []*types.Alert{a}, m) + } continue } validAlerts = append(validAlerts, a) @@ -456,8 +464,15 @@ func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...* typ: errorInternal, err: err, }, nil) + if api.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{"msg": err.Error()} + api.alertLCObserver.Observe(alertobserver.EventAlertRejected, validAlerts, m) + } return } + if api.alertLCObserver != nil { + api.alertLCObserver.Observe(alertobserver.EventAlertReceived, validAlerts, alertobserver.AlertEventMeta{}) + } if validationErrs.Len() > 0 { api.respondError(w, apiError{ diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/api.go b/vendor/github.com/prometheus/alertmanager/api/v2/api.go index 1ddb2bcbae..b31d5bbfd2 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/api.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/api.go @@ -32,6 +32,7 @@ import ( "github.com/prometheus/common/version" "github.com/rs/cors" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/api/metrics" open_api_models "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/api/v2/restapi" @@ -68,8 +69,9 @@ type API struct { route *dispatch.Route setAlertStatus setAlertStatusFn - logger log.Logger - m *metrics.Alerts + logger log.Logger + m *metrics.Alerts + alertLCObserver alertobserver.LifeCycleObserver Handler http.Handler } @@ -89,16 +91,18 @@ func NewAPI( peer cluster.ClusterPeer, l log.Logger, r prometheus.Registerer, + o alertobserver.LifeCycleObserver, ) (*API, error) { api := API{ - alerts: alerts, - getAlertStatus: sf, - alertGroups: gf, - peer: peer, - silences: silences, - logger: l, - m: metrics.NewAlerts("v2", r), - uptime: time.Now(), + alerts: alerts, + getAlertStatus: sf, + alertGroups: gf, + peer: peer, + silences: silences, + logger: l, + m: metrics.NewAlerts("v2", r), + uptime: time.Now(), + alertLCObserver: o, } // Load embedded swagger file. @@ -350,12 +354,20 @@ func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware. if err := a.Validate(); err != nil { validationErrs.Add(err) api.m.Invalid().Inc() + if api.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{"msg": err.Error()} + api.alertLCObserver.Observe(alertobserver.EventAlertRejected, []*types.Alert{a}, m) + } continue } validAlerts = append(validAlerts, a) } if err := api.alerts.Put(validAlerts...); err != nil { level.Error(logger).Log("msg", "Failed to create alerts", "err", err) + if api.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{"msg": err.Error()} + api.alertLCObserver.Observe(alertobserver.EventAlertRejected, validAlerts, m) + } return alert_ops.NewPostAlertsInternalServerError().WithPayload(err.Error()) } @@ -363,6 +375,9 @@ func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware. level.Error(logger).Log("msg", "Failed to validate alerts", "err", validationErrs.Error()) return alert_ops.NewPostAlertsBadRequest().WithPayload(validationErrs.Error()) } + if api.alertLCObserver != nil { + api.alertLCObserver.Observe(alertobserver.EventAlertReceived, validAlerts, alertobserver.AlertEventMeta{}) + } return alert_ops.NewPostAlertsOK() } diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/testing.go b/vendor/github.com/prometheus/alertmanager/api/v2/testing.go index c813d350d3..9e8f31db0a 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/testing.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/testing.go @@ -19,11 +19,14 @@ import ( "time" "github.com/go-openapi/strfmt" + "github.com/prometheus/common/model" "github.com/stretchr/testify/require" open_api_models "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/pkg/labels" + "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/silence/silencepb" + "github.com/prometheus/alertmanager/types" ) func createSilence(t *testing.T, ID, creator string, start, ends time.Time) (open_api_models.PostableSilence, []byte) { @@ -68,3 +71,48 @@ func createLabelMatcher(t *testing.T, name, value string, matchType labels.Match matcher, _ := labels.NewMatcher(matchType, name, value) return matcher } + +func createAlert(t *testing.T, start, ends time.Time) (open_api_models.PostableAlerts, []byte) { + startsAt := strfmt.DateTime(start) + endsAt := strfmt.DateTime(ends) + + alert := open_api_models.PostableAlert{ + StartsAt: startsAt, + EndsAt: endsAt, + Annotations: open_api_models.LabelSet{"annotation1": "some text"}, + Alert: open_api_models.Alert{ + Labels: open_api_models.LabelSet{"label1": "test1"}, + GeneratorURL: "http://localhost:3000", + }, + } + alerts := open_api_models.PostableAlerts{} + alerts = append(alerts, &alert) + b, err := json.Marshal(alerts) + require.NoError(t, err) + return alerts, b +} + +type fakeAlerts struct { + fps map[model.Fingerprint]int + alerts []*types.Alert + err error +} + +func newFakeAlerts(alerts []*types.Alert) *fakeAlerts { + fps := make(map[model.Fingerprint]int) + for i, a := range alerts { + fps[a.Fingerprint()] = i + } + f := &fakeAlerts{ + alerts: alerts, + fps: fps, + } + return f +} + +func (f *fakeAlerts) Subscribe() provider.AlertIterator { return nil } +func (f *fakeAlerts) Get(model.Fingerprint) (*types.Alert, error) { return nil, nil } +func (f *fakeAlerts) GetPending() provider.AlertIterator { return nil } +func (f *fakeAlerts) Put(alerts ...*types.Alert) error { + return f.err +} diff --git a/vendor/github.com/prometheus/alertmanager/config/config.go b/vendor/github.com/prometheus/alertmanager/config/config.go index ae5f786ee8..bf236aefe0 100644 --- a/vendor/github.com/prometheus/alertmanager/config/config.go +++ b/vendor/github.com/prometheus/alertmanager/config/config.go @@ -907,7 +907,7 @@ type Receiver struct { SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"` TelegramConfigs []*TelegramConfig `yaml:"telegram_configs,omitempty" json:"telegram_configs,omitempty"` WebexConfigs []*WebexConfig `yaml:"webex_configs,omitempty" json:"webex_configs,omitempty"` - MSTeamsConfigs []*MSTeamsConfig `yaml:"msteams_configs,omitempty" json:"teams_configs,omitempty"` + MSTeamsConfigs []*MSTeamsConfig `yaml:"msteams_configs,omitempty" json:"msteams_configs,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver. diff --git a/vendor/github.com/prometheus/alertmanager/config/notifiers.go b/vendor/github.com/prometheus/alertmanager/config/notifiers.go index 0da9e27ba2..2650db5f3b 100644 --- a/vendor/github.com/prometheus/alertmanager/config/notifiers.go +++ b/vendor/github.com/prometheus/alertmanager/config/notifiers.go @@ -503,11 +503,6 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if c.URL != nil && c.URLFile != "" { return fmt.Errorf("at most one of url & url_file must be configured") } - if c.URL != nil { - if c.URL.Scheme != "https" && c.URL.Scheme != "http" { - return fmt.Errorf("scheme required for webhook url") - } - } return nil } @@ -694,6 +689,7 @@ type PushoverConfig struct { Priority string `yaml:"priority,omitempty" json:"priority,omitempty"` Retry duration `yaml:"retry,omitempty" json:"retry,omitempty"` Expire duration `yaml:"expire,omitempty" json:"expire,omitempty"` + TTL duration `yaml:"ttl,omitempty" json:"ttl,omitempty"` HTML bool `yaml:"html" json:"html,omitempty"` } diff --git a/vendor/github.com/prometheus/alertmanager/dispatch/dispatch.go b/vendor/github.com/prometheus/alertmanager/dispatch/dispatch.go index 853d849688..833c70a544 100644 --- a/vendor/github.com/prometheus/alertmanager/dispatch/dispatch.go +++ b/vendor/github.com/prometheus/alertmanager/dispatch/dispatch.go @@ -25,6 +25,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/store" @@ -90,7 +91,8 @@ type Dispatcher struct { ctx context.Context cancel func() - logger log.Logger + logger log.Logger + alertLCObserver alertobserver.LifeCycleObserver } // Limits describes limits used by Dispatcher. @@ -111,19 +113,21 @@ func NewDispatcher( lim Limits, l log.Logger, m *DispatcherMetrics, + o alertobserver.LifeCycleObserver, ) *Dispatcher { if lim == nil { lim = nilLimits{} } disp := &Dispatcher{ - alerts: ap, - stage: s, - route: r, - timeout: to, - logger: log.With(l, "component", "dispatcher"), - metrics: m, - limits: lim, + alerts: ap, + stage: s, + route: r, + timeout: to, + logger: log.With(l, "component", "dispatcher"), + metrics: m, + limits: lim, + alertLCObserver: o, } return disp } @@ -319,13 +323,20 @@ func (d *Dispatcher) processAlert(alert *types.Alert, route *Route) { ag, ok := routeGroups[fp] if ok { ag.insert(alert) + if d.alertLCObserver != nil { + d.alertLCObserver.Observe(alertobserver.EventAlertAddedToAggrGroup, []*types.Alert{alert}, alertobserver.AlertEventMeta{"groupKey": ag.GroupKey()}) + } return } // If the group does not exist, create it. But check the limit first. if limit := d.limits.MaxNumberOfAggregationGroups(); limit > 0 && d.aggrGroupsNum >= limit { d.metrics.aggrGroupLimitReached.Inc() - level.Error(d.logger).Log("msg", "Too many aggregation groups, cannot create new group for alert", "groups", d.aggrGroupsNum, "limit", limit, "alert", alert.Name()) + errMsg := "Too many aggregation groups, cannot create new group for alert" + level.Error(d.logger).Log("msg", errMsg, "groups", d.aggrGroupsNum, "limit", limit, "alert", alert.Name()) + if d.alertLCObserver != nil { + d.alertLCObserver.Observe(alertobserver.EventAlertFailedAddToAggrGroup, []*types.Alert{alert}, alertobserver.AlertEventMeta{"msg": errMsg}) + } return } @@ -338,6 +349,9 @@ func (d *Dispatcher) processAlert(alert *types.Alert, route *Route) { // function, to make sure that when the run() will be executed the 1st // alert is already there. ag.insert(alert) + if d.alertLCObserver != nil { + d.alertLCObserver.Observe(alertobserver.EventAlertAddedToAggrGroup, []*types.Alert{alert}, alertobserver.AlertEventMeta{"groupKey": ag.GroupKey()}) + } go ag.run(func(ctx context.Context, alerts ...*types.Alert) bool { _, _, err := d.stage.Exec(ctx, d.logger, alerts...) diff --git a/vendor/github.com/prometheus/alertmanager/dispatch/route.go b/vendor/github.com/prometheus/alertmanager/dispatch/route.go index 4b3673c531..5ada178dab 100644 --- a/vendor/github.com/prometheus/alertmanager/dispatch/route.go +++ b/vendor/github.com/prometheus/alertmanager/dispatch/route.go @@ -180,6 +180,29 @@ func (r *Route) Key() string { return b.String() } +// ID returns a unique identifier for the route. +func (r *Route) ID() string { + b := strings.Builder{} + + position := -1 + if r.parent != nil { + // Find the position in the same level leaf. + for i, cr := range r.parent.Routes { + if cr == r { + position = i + break + } + } + } + b.WriteString(r.Key()) + + if position > -1 { + b.WriteRune('/') + b.WriteString(fmt.Sprint(position)) + } + return b.String() +} + // Walk traverses the route tree in depth-first order. func (r *Route) Walk(visit func(*Route)) { visit(r) diff --git a/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go b/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go new file mode 100644 index 0000000000..7a04499545 --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go @@ -0,0 +1,78 @@ +// Copyright 2023 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package featurecontrol + +import ( + "fmt" + "strings" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +const ( + fcReceiverNameInMetrics = "receiver-name-in-metrics" +) + +var AllowedFlags = []string{fcReceiverNameInMetrics} + +type Flagger interface { + EnableReceiverNamesInMetrics() bool +} + +type Flags struct { + logger log.Logger + enableReceiverNamesInMetrics bool +} + +func (f *Flags) EnableReceiverNamesInMetrics() bool { + return f.enableReceiverNamesInMetrics +} + +type flagOption func(flags *Flags) + +func enableReceiverNameInMetrics() flagOption { + return func(configs *Flags) { + configs.enableReceiverNamesInMetrics = true + } +} + +func NewFlags(logger log.Logger, features string) (Flagger, error) { + fc := &Flags{logger: logger} + opts := []flagOption{} + + if len(features) == 0 { + return NoopFlags{}, nil + } + + for _, feature := range strings.Split(features, ",") { + switch feature { + case fcReceiverNameInMetrics: + opts = append(opts, enableReceiverNameInMetrics()) + level.Warn(logger).Log("msg", "Experimental receiver name in metrics enabled") + default: + return nil, fmt.Errorf("Unknown option '%s' for --enable-feature", feature) + } + } + + for _, opt := range opts { + opt(fc) + } + + return fc, nil +} + +type NoopFlags struct{} + +func (n NoopFlags) EnableReceiverNamesInMetrics() bool { return false } diff --git a/vendor/github.com/prometheus/alertmanager/notify/notify.go b/vendor/github.com/prometheus/alertmanager/notify/notify.go index 0364a04909..29c06d752a 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/notify.go +++ b/vendor/github.com/prometheus/alertmanager/notify/notify.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "sort" + "strings" "sync" "time" @@ -28,6 +29,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" + "github.com/prometheus/alertmanager/alertobserver" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/alertmanager/inhibit" "github.com/prometheus/alertmanager/nflog" "github.com/prometheus/alertmanager/nflog/nflogpb" @@ -61,19 +64,21 @@ type Notifier interface { // Integration wraps a notifier and its configuration to be uniquely identified // by name and index from its origin in the configuration. type Integration struct { - notifier Notifier - rs ResolvedSender - name string - idx int + notifier Notifier + rs ResolvedSender + name string + idx int + receiverName string } // NewIntegration returns a new integration. -func NewIntegration(notifier Notifier, rs ResolvedSender, name string, idx int) Integration { +func NewIntegration(notifier Notifier, rs ResolvedSender, name string, idx int, receiverName string) Integration { return Integration{ - notifier: notifier, - rs: rs, - name: name, - idx: idx, + notifier: notifier, + rs: rs, + name: name, + idx: idx, + receiverName: receiverName, } } @@ -249,40 +254,86 @@ type Metrics struct { numNotificationRequestsTotal *prometheus.CounterVec numNotificationRequestsFailedTotal *prometheus.CounterVec notificationLatencySeconds *prometheus.HistogramVec + + ff featurecontrol.Flagger } -func NewMetrics(r prometheus.Registerer) *Metrics { +func NewMetrics(r prometheus.Registerer, ff featurecontrol.Flagger) *Metrics { + labels := []string{"integration"} + + if ff.EnableReceiverNamesInMetrics() { + labels = append(labels, "receiver_name") + } + m := &Metrics{ numNotifications: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "alertmanager", Name: "notifications_total", Help: "The total number of attempted notifications.", - }, []string{"integration"}), + }, labels), numTotalFailedNotifications: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "alertmanager", Name: "notifications_failed_total", Help: "The total number of failed notifications.", - }, []string{"integration", "reason"}), + }, append(labels, "reason")), numNotificationRequestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "alertmanager", Name: "notification_requests_total", Help: "The total number of attempted notification requests.", - }, []string{"integration"}), + }, labels), numNotificationRequestsFailedTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "alertmanager", Name: "notification_requests_failed_total", Help: "The total number of failed notification requests.", - }, []string{"integration"}), + }, labels), notificationLatencySeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "alertmanager", Name: "notification_latency_seconds", Help: "The latency of notifications in seconds.", Buckets: []float64{1, 5, 10, 15, 20}, - }, []string{"integration"}), + }, labels), + ff: ff, + } + + r.MustRegister( + m.numNotifications, m.numTotalFailedNotifications, + m.numNotificationRequestsTotal, m.numNotificationRequestsFailedTotal, + m.notificationLatencySeconds, + ) + + return m +} + +func (m *Metrics) InitializeFor(receiver map[string][]Integration) { + if m.ff.EnableReceiverNamesInMetrics() { + + // Reset the vectors to take into account receiver names changing after hot reloads. + m.numNotifications.Reset() + m.numNotificationRequestsTotal.Reset() + m.numNotificationRequestsFailedTotal.Reset() + m.notificationLatencySeconds.Reset() + m.numTotalFailedNotifications.Reset() + + for name, integrations := range receiver { + for _, integration := range integrations { + + m.numNotifications.WithLabelValues(integration.Name(), name) + m.numNotificationRequestsTotal.WithLabelValues(integration.Name(), name) + m.numNotificationRequestsFailedTotal.WithLabelValues(integration.Name(), name) + m.notificationLatencySeconds.WithLabelValues(integration.Name(), name) + + for _, reason := range possibleFailureReasonCategory { + m.numTotalFailedNotifications.WithLabelValues(integration.Name(), name, reason) + } + } + } + + return } + + // When the feature flag is not enabled, we just carry on registering _all_ the integrations. for _, integration := range []string{ "email", - "msteams", "pagerduty", "wechat", "pushover", @@ -292,6 +343,9 @@ func NewMetrics(r prometheus.Registerer) *Metrics { "victorops", "sns", "telegram", + "discord", + "webex", + "msteams", } { m.numNotifications.WithLabelValues(integration) m.numNotificationRequestsTotal.WithLabelValues(integration) @@ -302,21 +356,17 @@ func NewMetrics(r prometheus.Registerer) *Metrics { m.numTotalFailedNotifications.WithLabelValues(integration, reason) } } - r.MustRegister( - m.numNotifications, m.numTotalFailedNotifications, - m.numNotificationRequestsTotal, m.numNotificationRequestsFailedTotal, - m.notificationLatencySeconds, - ) - return m } type PipelineBuilder struct { metrics *Metrics + ff featurecontrol.Flagger } -func NewPipelineBuilder(r prometheus.Registerer) *PipelineBuilder { +func NewPipelineBuilder(r prometheus.Registerer, ff featurecontrol.Flagger) *PipelineBuilder { return &PipelineBuilder{ - metrics: NewMetrics(r), + metrics: NewMetrics(r, ff), + ff: ff, } } @@ -329,19 +379,29 @@ func (pb *PipelineBuilder) New( times map[string][]timeinterval.TimeInterval, notificationLog NotificationLog, peer Peer, + o alertobserver.LifeCycleObserver, ) RoutingStage { - rs := make(RoutingStage, len(receivers)) + rs := RoutingStage{ + stages: make(map[string]Stage, len(receivers)), + alertLCObserver: o, + } ms := NewGossipSettleStage(peer) - is := NewMuteStage(inhibitor) + is := NewMuteStage(inhibitor, o) tas := NewTimeActiveStage(times) tms := NewTimeMuteStage(times) - ss := NewMuteStage(silencer) + ss := NewMuteStage(silencer, o) for name := range receivers { - st := createReceiverStage(name, receivers[name], wait, notificationLog, pb.metrics) - rs[name] = MultiStage{ms, is, tas, tms, ss, st} + st := createReceiverStage(name, receivers[name], wait, notificationLog, pb.metrics, o) + rs.stages[name] = MultiStage{ + alertLCObserver: o, + stages: []Stage{ms, is, tas, tms, ss, st}, + } } + + pb.metrics.InitializeFor(receivers) + return rs } @@ -352,6 +412,7 @@ func createReceiverStage( wait func() time.Duration, notificationLog NotificationLog, metrics *Metrics, + o alertobserver.LifeCycleObserver, ) Stage { var fs FanoutStage for i := range integrations { @@ -360,20 +421,23 @@ func createReceiverStage( Integration: integrations[i].Name(), Idx: uint32(integrations[i].Index()), } - var s MultiStage + var s []Stage s = append(s, NewWaitStage(wait)) s = append(s, NewDedupStage(&integrations[i], notificationLog, recv)) - s = append(s, NewRetryStage(integrations[i], name, metrics)) + s = append(s, NewRetryStage(integrations[i], name, metrics, o)) s = append(s, NewSetNotifiesStage(notificationLog, recv)) - fs = append(fs, s) + fs = append(fs, MultiStage{stages: s, alertLCObserver: o}) } return fs } // RoutingStage executes the inner stages based on the receiver specified in // the context. -type RoutingStage map[string]Stage +type RoutingStage struct { + stages map[string]Stage + alertLCObserver alertobserver.LifeCycleObserver +} // Exec implements the Stage interface. func (rs RoutingStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { @@ -382,21 +446,28 @@ func (rs RoutingStage) Exec(ctx context.Context, l log.Logger, alerts ...*types. return ctx, nil, errors.New("receiver missing") } - s, ok := rs[receiver] + s, ok := rs.stages[receiver] if !ok { return ctx, nil, errors.New("stage for receiver missing") } + if rs.alertLCObserver != nil { + rs.alertLCObserver.Observe(alertobserver.EventAlertPipelineStart, alerts, alertobserver.AlertEventMeta{"ctx": ctx}) + } + return s.Exec(ctx, l, alerts...) } // A MultiStage executes a series of stages sequentially. -type MultiStage []Stage +type MultiStage struct { + stages []Stage + alertLCObserver alertobserver.LifeCycleObserver +} // Exec implements the Stage interface. func (ms MultiStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { var err error - for _, s := range ms { + for _, s := range ms.stages { if len(alerts) == 0 { return ctx, nil, nil } @@ -405,6 +476,10 @@ func (ms MultiStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Al if err != nil { return ctx, nil, err } + if ms.alertLCObserver != nil { + p := strings.Split(fmt.Sprintf("%T", s), ".") + ms.alertLCObserver.Observe(alertobserver.EventAlertPipelinePassStage, alerts, alertobserver.AlertEventMeta{"ctx": ctx, "stageName": p[len(p)-1]}) + } } return ctx, alerts, nil } @@ -458,25 +533,32 @@ func (n *GossipSettleStage) Exec(ctx context.Context, _ log.Logger, alerts ...*t // MuteStage filters alerts through a Muter. type MuteStage struct { - muter types.Muter + muter types.Muter + alertLCObserver alertobserver.LifeCycleObserver } // NewMuteStage return a new MuteStage. -func NewMuteStage(m types.Muter) *MuteStage { - return &MuteStage{muter: m} +func NewMuteStage(m types.Muter, o alertobserver.LifeCycleObserver) *MuteStage { + return &MuteStage{muter: m, alertLCObserver: o} } // Exec implements the Stage interface. func (n *MuteStage) Exec(ctx context.Context, _ log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { var filtered []*types.Alert + var muted []*types.Alert for _, a := range alerts { // TODO(fabxc): increment total alerts counter. // Do not send the alert if muted. if !n.muter.Mutes(a.Labels) { filtered = append(filtered, a) + } else { + muted = append(muted, a) } // TODO(fabxc): increment muted alerts counter if muted. } + if n.alertLCObserver != nil { + n.alertLCObserver.Observe(alertobserver.EventAlertMuted, muted, alertobserver.AlertEventMeta{"ctx": ctx}) + } return ctx, filtered, nil } @@ -649,35 +731,60 @@ func (n *DedupStage) Exec(ctx context.Context, _ log.Logger, alerts ...*types.Al // RetryStage notifies via passed integration with exponential backoff until it // succeeds. It aborts if the context is canceled or timed out. type RetryStage struct { - integration Integration - groupName string - metrics *Metrics + integration Integration + groupName string + metrics *Metrics + labelValues []string + alertLCObserver alertobserver.LifeCycleObserver } // NewRetryStage returns a new instance of a RetryStage. -func NewRetryStage(i Integration, groupName string, metrics *Metrics) *RetryStage { +func NewRetryStage(i Integration, groupName string, metrics *Metrics, o alertobserver.LifeCycleObserver) *RetryStage { + labelValues := []string{i.Name()} + + if metrics.ff.EnableReceiverNamesInMetrics() { + labelValues = append(labelValues, i.receiverName) + } + return &RetryStage{ - integration: i, - groupName: groupName, - metrics: metrics, + integration: i, + groupName: groupName, + metrics: metrics, + labelValues: labelValues, + alertLCObserver: o, } } func (r RetryStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { - r.metrics.numNotifications.WithLabelValues(r.integration.Name()).Inc() - ctx, alerts, err := r.exec(ctx, l, alerts...) + r.metrics.numNotifications.WithLabelValues(r.labelValues...).Inc() + ctx, alerts, sent, err := r.exec(ctx, l, alerts...) failureReason := DefaultReason.String() if err != nil { if e, ok := errors.Cause(err).(*ErrorWithReason); ok { failureReason = e.Reason.String() } - r.metrics.numTotalFailedNotifications.WithLabelValues(r.integration.Name(), failureReason).Inc() + r.metrics.numTotalFailedNotifications.WithLabelValues(append(r.labelValues, failureReason)...).Inc() + if r.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{ + "ctx": ctx, + "integration": r.integration.Name(), + "stageName": "RetryStage", + } + r.alertLCObserver.Observe(alertobserver.EventAlertSendFailed, sent, m) + } + } else if r.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{ + "ctx": ctx, + "integration": r.integration.Name(), + "stageName": "RetryStage", + } + r.alertLCObserver.Observe(alertobserver.EventAlertSent, sent, m) } return ctx, alerts, err } -func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { +func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, []*types.Alert, error) { var sent []*types.Alert // If we shouldn't send notifications for resolved alerts, but there are only @@ -686,10 +793,10 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale if !r.integration.SendResolved() { firing, ok := FiringAlerts(ctx) if !ok { - return ctx, nil, errors.New("firing alerts missing") + return ctx, nil, nil, errors.New("firing alerts missing") } if len(firing) == 0 { - return ctx, alerts, nil + return ctx, alerts, sent, nil } for _, a := range alerts { if a.Status() != model.AlertResolved { @@ -725,7 +832,7 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale iErr = ctx.Err() } - return ctx, nil, errors.Wrapf(iErr, "%s/%s: notify retry canceled after %d attempts", r.groupName, r.integration.String(), i) + return ctx, nil, sent, errors.Wrapf(iErr, "%s/%s: notify retry canceled after %d attempts", r.groupName, r.integration.String(), i) default: } @@ -733,12 +840,12 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale case <-tick.C: now := time.Now() retry, err := r.integration.Notify(ctx, sent...) - r.metrics.notificationLatencySeconds.WithLabelValues(r.integration.Name()).Observe(time.Since(now).Seconds()) - r.metrics.numNotificationRequestsTotal.WithLabelValues(r.integration.Name()).Inc() + r.metrics.notificationLatencySeconds.WithLabelValues(r.labelValues...).Observe(time.Since(now).Seconds()) + r.metrics.numNotificationRequestsTotal.WithLabelValues(r.labelValues...).Inc() if err != nil { - r.metrics.numNotificationRequestsFailedTotal.WithLabelValues(r.integration.Name()).Inc() + r.metrics.numNotificationRequestsFailedTotal.WithLabelValues(r.labelValues...).Inc() if !retry { - return ctx, alerts, errors.Wrapf(err, "%s/%s: notify retry canceled due to unrecoverable error after %d attempts", r.groupName, r.integration.String(), i) + return ctx, alerts, sent, errors.Wrapf(err, "%s/%s: notify retry canceled due to unrecoverable error after %d attempts", r.groupName, r.integration.String(), i) } if ctx.Err() == nil && (iErr == nil || err.Error() != iErr.Error()) { // Log the error if the context isn't done and the error isn't the same as before. @@ -753,16 +860,15 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale if i <= 1 { lvl = level.Debug(log.With(l, "alerts", fmt.Sprintf("%v", alerts))) } - lvl.Log("msg", "Notify success", "attempts", i) - return ctx, alerts, nil + + return ctx, alerts, sent, nil } case <-ctx.Done(): if iErr == nil { iErr = ctx.Err() } - - return ctx, nil, errors.Wrapf(iErr, "%s/%s: notify retry canceled after %d attempts", r.groupName, r.integration.String(), i) + return ctx, nil, sent, errors.Wrapf(iErr, "%s/%s: notify retry canceled after %d attempts", r.groupName, r.integration.String(), i) } } } diff --git a/vendor/github.com/prometheus/alertmanager/notify/pushover/pushover.go b/vendor/github.com/prometheus/alertmanager/notify/pushover/pushover.go index 9964270a5e..a8f4644f65 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/pushover/pushover.go +++ b/vendor/github.com/prometheus/alertmanager/notify/pushover/pushover.go @@ -147,6 +147,12 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) parameters.Add("expire", fmt.Sprintf("%d", int64(time.Duration(n.conf.Expire).Seconds()))) parameters.Add("device", tmpl(n.conf.Device)) parameters.Add("sound", tmpl(n.conf.Sound)) + + newttl := int64(time.Duration(n.conf.TTL).Seconds()) + if newttl > 0 { + parameters.Add("ttl", fmt.Sprintf("%d", newttl)) + } + if err != nil { return false, err } diff --git a/vendor/github.com/prometheus/alertmanager/ui/web.go b/vendor/github.com/prometheus/alertmanager/ui/web.go index 4d41a30732..3c2cd8784c 100644 --- a/vendor/github.com/prometheus/alertmanager/ui/web.go +++ b/vendor/github.com/prometheus/alertmanager/ui/web.go @@ -95,4 +95,4 @@ func disableCaching(w http.ResponseWriter) { w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") // Prevent proxies from caching. -} +} \ No newline at end of file diff --git a/vendor/github.com/prometheus/common/assets/LICENSE b/vendor/github.com/prometheus/common/assets/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/github.com/prometheus/common/assets/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/prometheus/common/assets/Makefile b/vendor/github.com/prometheus/common/assets/Makefile new file mode 100644 index 0000000000..9301ab15e4 --- /dev/null +++ b/vendor/github.com/prometheus/common/assets/Makefile @@ -0,0 +1,18 @@ +# Copyright 2018 The Prometheus Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include ../Makefile.common + +.PHONY: test + @echo ">> Running assets tests" + test:: deps check_license unused common-test diff --git a/vendor/github.com/prometheus/common/assets/embed_gzip.go b/vendor/github.com/prometheus/common/assets/embed_gzip.go new file mode 100644 index 0000000000..12ffcab7fa --- /dev/null +++ b/vendor/github.com/prometheus/common/assets/embed_gzip.go @@ -0,0 +1,123 @@ +// Copyright 2021 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package assets + +import ( + "compress/gzip" + "embed" + "io" + "io/fs" + "time" +) + +const ( + gzipSuffix = ".gz" +) + +type FileSystem struct { + embed embed.FS +} + +func New(fs embed.FS) FileSystem { + return FileSystem{fs} +} + +// Open implements the fs.FS interface. +func (compressed FileSystem) Open(path string) (fs.File, error) { + // If we have the file in our embed FS, just return that as it could be a dir. + var f fs.File + if f, err := compressed.embed.Open(path); err == nil { + return f, nil + } + + f, err := compressed.embed.Open(path + gzipSuffix) + if err != nil { + return f, err + } + // Read the decompressed content into a buffer. + gr, err := gzip.NewReader(f) + if err != nil { + return f, err + } + defer gr.Close() + + c, err := io.ReadAll(gr) + if err != nil { + return f, err + } + // Wrap everything in our custom File. + return &File{file: f, content: c}, nil +} + +type File struct { + // The underlying file. + file fs.File + // The decrompressed content, needed to return an accurate size. + content []byte + // Offset for calls to Read(). + offset int +} + +// Stat implements the fs.File interface. +func (f File) Stat() (fs.FileInfo, error) { + stat, err := f.file.Stat() + if err != nil { + return stat, err + } + return FileInfo{stat, int64(len(f.content))}, nil +} + +// Read implements the fs.File interface. +func (f *File) Read(buf []byte) (int, error) { + if len(buf) > len(f.content)-f.offset { + buf = buf[0:len(f.content[f.offset:])] + } + n := copy(buf, f.content[f.offset:]) + if n == len(f.content)-f.offset { + return n, io.EOF + } + f.offset += n + return n, nil +} + +// Close implements the fs.File interface. +func (f File) Close() error { + return f.file.Close() +} + +type FileInfo struct { + fi fs.FileInfo + actualSize int64 +} + +// Name implements the fs.FileInfo interface. +func (fi FileInfo) Name() string { + name := fi.fi.Name() + return name[:len(name)-len(gzipSuffix)] +} + +// Size implements the fs.FileInfo interface. +func (fi FileInfo) Size() int64 { return fi.actualSize } + +// Mode implements the fs.FileInfo interface. +func (fi FileInfo) Mode() fs.FileMode { return fi.fi.Mode() } + +// ModTime implements the fs.FileInfo interface. +func (fi FileInfo) ModTime() time.Time { return fi.fi.ModTime() } + +// IsDir implements the fs.FileInfo interface. +func (fi FileInfo) IsDir() bool { return fi.fi.IsDir() } + +// Sys implements the fs.FileInfo interface. +func (fi FileInfo) Sys() interface{} { return nil } diff --git a/vendor/github.com/prometheus/common/assets/testdata/both b/vendor/github.com/prometheus/common/assets/testdata/both new file mode 100644 index 0000000000..257cc5642c --- /dev/null +++ b/vendor/github.com/prometheus/common/assets/testdata/both @@ -0,0 +1 @@ +foo diff --git a/vendor/github.com/prometheus/common/assets/testdata/both.gz b/vendor/github.com/prometheus/common/assets/testdata/both.gz new file mode 100644 index 0000000000000000000000000000000000000000..8db323b0889361529199fd16ca6298c3feda751e GIT binary patch literal 29 kcmb2|=HTGJ*ptY>oRnXZ!Qg%R{BtIT6{$vbEDQ_`0Ex^9#{d8T literal 0 HcmV?d00001 diff --git a/vendor/github.com/prometheus/common/assets/testdata/compressed.gz b/vendor/github.com/prometheus/common/assets/testdata/compressed.gz new file mode 100644 index 0000000000000000000000000000000000000000..fe6ff26a49b3ceeca70829cf72a6d93d48d7ac44 GIT binary patch literal 35 qcmb2|=HTGE*ptY>oSdIqP?TC+oSMSmefs=!CWaNMMs+L<3=9C#dkabc literal 0 HcmV?d00001 diff --git a/vendor/github.com/prometheus/common/assets/testdata/uncompressed b/vendor/github.com/prometheus/common/assets/testdata/uncompressed new file mode 100644 index 0000000000..257cc5642c --- /dev/null +++ b/vendor/github.com/prometheus/common/assets/testdata/uncompressed @@ -0,0 +1 @@ +foo diff --git a/vendor/github.com/prometheus/common/server/static_file_server.go b/vendor/github.com/prometheus/common/server/static_file_server.go new file mode 100644 index 0000000000..605945d764 --- /dev/null +++ b/vendor/github.com/prometheus/common/server/static_file_server.go @@ -0,0 +1,53 @@ +// Copyright 2019 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "net/http" + "path/filepath" +) + +var mimeTypes = map[string]string{ + ".cjs": "application/javascript", + ".css": "text/css", + ".eot": "font/eot", + ".gif": "image/gif", + ".ico": "image/x-icon", + ".jpg": "image/jpeg", + ".js": "application/javascript", + ".json": "application/json", + ".less": "text/plain", + ".map": "application/json", + ".otf": "font/otf", + ".png": "image/png", + ".svg": "image/svg+xml", + ".ttf": "font/ttf", + ".txt": "text/plain", + ".woff": "font/woff", + ".woff2": "font/woff2", +} + +func StaticFileServer(root http.FileSystem) http.Handler { + return http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + fileExt := filepath.Ext(r.URL.Path) + + if t, ok := mimeTypes[fileExt]; ok { + w.Header().Set("Content-Type", t) + } + + http.FileServer(root).ServeHTTP(w, r) + }, + ) +} diff --git a/vendor/github.com/rs/cors/README.md b/vendor/github.com/rs/cors/README.md index 0ad3e94e1b..fe12fa671d 100644 --- a/vendor/github.com/rs/cors/README.md +++ b/vendor/github.com/rs/cors/README.md @@ -1,4 +1,4 @@ -# Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors) [![Coverage](http://gocover.io/_badge/github.com/rs/cors)](http://gocover.io/github.com/rs/cors) +# Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![Go Coverage](https://github.com/rs/cors/wiki/coverage.svg)](https://raw.githack.com/wiki/rs/cors/coverage.html) CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang. @@ -102,15 +102,18 @@ See [API documentation](http://godoc.org/github.com/rs/cors) for more info. ## Benchmarks - BenchmarkWithout 20000000 64.6 ns/op 8 B/op 1 allocs/op - BenchmarkDefault 3000000 469 ns/op 114 B/op 2 allocs/op - BenchmarkAllowedOrigin 3000000 608 ns/op 114 B/op 2 allocs/op - BenchmarkPreflight 20000000 73.2 ns/op 0 B/op 0 allocs/op - BenchmarkPreflightHeader 20000000 73.6 ns/op 0 B/op 0 allocs/op - BenchmarkParseHeaderList 2000000 847 ns/op 184 B/op 6 allocs/op - BenchmarkParse…Single 5000000 290 ns/op 32 B/op 3 allocs/op - BenchmarkParse…Normalized 2000000 776 ns/op 160 B/op 6 allocs/op - +goos: darwin +goarch: arm64 +pkg: github.com/rs/cors +BenchmarkWithout-10 352671961 3.317 ns/op 0 B/op 0 allocs/op +BenchmarkDefault-10 18261723 65.63 ns/op 0 B/op 0 allocs/op +BenchmarkAllowedOrigin-10 13309591 90.21 ns/op 0 B/op 0 allocs/op +BenchmarkPreflight-10 7247728 166.9 ns/op 0 B/op 0 allocs/op +BenchmarkPreflightHeader-10 5915437 202.9 ns/op 0 B/op 0 allocs/op +BenchmarkWildcard/match-10 250336476 4.784 ns/op 0 B/op 0 allocs/op +BenchmarkWildcard/too_short-10 1000000000 0.6354 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/rs/cors 9.613s ## Licenses All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE). diff --git a/vendor/github.com/rs/cors/cors.go b/vendor/github.com/rs/cors/cors.go index 5669a67f89..69f89d8f94 100644 --- a/vendor/github.com/rs/cors/cors.go +++ b/vendor/github.com/rs/cors/cors.go @@ -4,15 +4,15 @@ as defined by http://www.w3.org/TR/cors/ You can configure it by passing an option struct to cors.New: - c := cors.New(cors.Options{ - AllowedOrigins: []string{"foo.com"}, - AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, - AllowCredentials: true, - }) + c := cors.New(cors.Options{ + AllowedOrigins: []string{"foo.com"}, + AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, + AllowCredentials: true, + }) Then insert the handler in the chain: - handler = c.Handler(handler) + handler = c.Handler(handler) See Options documentation for more options. @@ -28,6 +28,10 @@ import ( "strings" ) +var headerVaryOrigin = []string{"Origin"} +var headerOriginAll = []string{"*"} +var headerTrue = []string{"true"} + // Options is a configuration container to setup the CORS middleware. type Options struct { // AllowedOrigins is a list of origins a cross-domain request can be executed from. @@ -37,27 +41,39 @@ type Options struct { // Only one wildcard can be used per origin. // Default value is ["*"] AllowedOrigins []string - // AllowOriginFunc is a custom function to validate the origin. It take the origin - // as argument and returns true if allowed or false otherwise. If this option is - // set, the content of AllowedOrigins is ignored. + // AllowOriginFunc is a custom function to validate the origin. It take the + // origin as argument and returns true if allowed or false otherwise. If + // this option is set, the content of `AllowedOrigins` is ignored. AllowOriginFunc func(origin string) bool - // AllowOriginRequestFunc is a custom function to validate the origin. It takes the HTTP Request object and the origin as - // argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` - // and `AllowOriginFunc` is ignored. + // AllowOriginRequestFunc is a custom function to validate the origin. It + // takes the HTTP Request object and the origin as argument and returns true + // if allowed or false otherwise. If headers are used take the decision, + // consider using AllowOriginVaryRequestFunc instead. If this option is set, + // the content of `AllowedOrigins`, `AllowOriginFunc` are ignored. AllowOriginRequestFunc func(r *http.Request, origin string) bool + // AllowOriginVaryRequestFunc is a custom function to validate the origin. + // It takes the HTTP Request object and the origin as argument and returns + // true if allowed or false otherwise with a list of headers used to take + // that decision if any so they can be added to the Vary header. If this + // option is set, the content of `AllowedOrigins`, `AllowOriginFunc` and + // `AllowOriginRequestFunc` are ignored. + AllowOriginVaryRequestFunc func(r *http.Request, origin string) (bool, []string) // AllowedMethods is a list of methods the client is allowed to use with // cross-domain requests. Default value is simple methods (HEAD, GET and POST). AllowedMethods []string // AllowedHeaders is list of non simple headers the client is allowed to use with // cross-domain requests. // If the special "*" value is present in the list, all headers will be allowed. - // Default value is [] but "Origin" is always appended to the list. + // Default value is []. AllowedHeaders []string // ExposedHeaders indicates which headers are safe to expose to the API of a CORS // API specification ExposedHeaders []string // MaxAge indicates how long (in seconds) the results of a preflight request - // can be cached + // can be cached. Default value is 0, which stands for no + // Access-Control-Max-Age header to be sent back, resulting in browsers + // using their default value (5s by spec). If you need to force a 0 max-age, + // set `MaxAge` to a negative value (ie: -1). MaxAge int // AllowCredentials indicates whether the request can include user credentials like // cookies, HTTP authentication or client side SSL certificates. @@ -73,6 +89,8 @@ type Options struct { OptionsSuccessStatus int // Debugging flag adds additional output to debug server side CORS issues Debug bool + // Adds a custom logger, implies Debug is true + Logger Logger } // Logger generic interface for logger @@ -89,16 +107,15 @@ type Cors struct { // List of allowed origins containing wildcards allowedWOrigins []wildcard // Optional origin validator function - allowOriginFunc func(origin string) bool - // Optional origin validator (with request) function - allowOriginRequestFunc func(r *http.Request, origin string) bool + allowOriginFunc func(r *http.Request, origin string) (bool, []string) // Normalized list of allowed headers allowedHeaders []string // Normalized list of allowed methods allowedMethods []string - // Normalized list of exposed headers + // Pre-computed normalized list of exposed headers exposedHeaders []string - maxAge int + // Pre-computed maxAge header value + maxAge []string // Set to true when allowed origins contains a "*" allowedOriginsAll bool // Set to true when allowed headers contains a "*" @@ -108,30 +125,40 @@ type Cors struct { allowCredentials bool allowPrivateNetwork bool optionPassthrough bool + preflightVary []string } // New creates a new Cors handler with the provided options. func New(options Options) *Cors { c := &Cors{ - exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), - allowOriginFunc: options.AllowOriginFunc, - allowOriginRequestFunc: options.AllowOriginRequestFunc, - allowCredentials: options.AllowCredentials, - allowPrivateNetwork: options.AllowPrivateNetwork, - maxAge: options.MaxAge, - optionPassthrough: options.OptionsPassthrough, + allowCredentials: options.AllowCredentials, + allowPrivateNetwork: options.AllowPrivateNetwork, + optionPassthrough: options.OptionsPassthrough, + Log: options.Logger, } if options.Debug && c.Log == nil { c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags) } + if options.AllowOriginVaryRequestFunc != nil { + c.allowOriginFunc = options.AllowOriginVaryRequestFunc + } else if options.AllowOriginRequestFunc != nil { + c.allowOriginFunc = func(r *http.Request, origin string) (bool, []string) { + return options.AllowOriginRequestFunc(r, origin), nil + } + } else if options.AllowOriginFunc != nil { + c.allowOriginFunc = func(r *http.Request, origin string) (bool, []string) { + return options.AllowOriginFunc(origin), nil + } + } + // Normalize options - // Note: for origins and methods matching, the spec requires a case-sensitive matching. + // Note: for origins matching, the spec requires a case-sensitive matching. // As it may error prone, we chose to ignore the spec here. // Allowed Origins if len(options.AllowedOrigins) == 0 { - if options.AllowOriginFunc == nil && options.AllowOriginRequestFunc == nil { + if c.allowOriginFunc == nil { // Default is all origins c.allowedOriginsAll = true } @@ -160,10 +187,9 @@ func New(options Options) *Cors { // Allowed Headers if len(options.AllowedHeaders) == 0 { // Use sensible defaults - c.allowedHeaders = []string{"Origin", "Accept", "Content-Type", "X-Requested-With"} + c.allowedHeaders = []string{"Accept", "Content-Type", "X-Requested-With"} } else { - // Origin is always appended as some browsers will always request for this header at preflight - c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey) + c.allowedHeaders = convert(options.AllowedHeaders, http.CanonicalHeaderKey) for _, h := range options.AllowedHeaders { if h == "*" { c.allowedHeadersAll = true @@ -178,7 +204,7 @@ func New(options Options) *Cors { // Default is spec's "simple" methods c.allowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead} } else { - c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper) + c.allowedMethods = options.AllowedMethods } // Options Success Status Code @@ -188,6 +214,25 @@ func New(options Options) *Cors { c.optionsSuccessStatus = options.OptionsSuccessStatus } + // Pre-compute exposed headers header value + if len(options.ExposedHeaders) > 0 { + c.exposedHeaders = []string{strings.Join(convert(options.ExposedHeaders, http.CanonicalHeaderKey), ", ")} + } + + // Pre-compute prefight Vary header to save allocations + if c.allowPrivateNetwork { + c.preflightVary = []string{"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network"} + } else { + c.preflightVary = []string{"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"} + } + + // Precompute max-age + if options.MaxAge > 0 { + c.maxAge = []string{strconv.Itoa(options.MaxAge)} + } else if options.MaxAge < 0 { + c.maxAge = []string{"0"} + } + return c } @@ -284,18 +329,21 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { // Always set Vary headers // see https://github.com/rs/cors/issues/10, // https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001 - headers.Add("Vary", "Origin") - headers.Add("Vary", "Access-Control-Request-Method") - headers.Add("Vary", "Access-Control-Request-Headers") - if c.allowPrivateNetwork { - headers.Add("Vary", "Access-Control-Request-Private-Network") + if vary, found := headers["Vary"]; found { + headers["Vary"] = append(vary, c.preflightVary[0]) + } else { + headers["Vary"] = c.preflightVary + } + allowed, additionalVaryHeaders := c.isOriginAllowed(r, origin) + if len(additionalVaryHeaders) > 0 { + headers.Add("Vary", strings.Join(convert(additionalVaryHeaders, http.CanonicalHeaderKey), ", ")) } if origin == "" { c.logf(" Preflight aborted: empty origin") return } - if !c.isOriginAllowed(r, origin) { + if !allowed { c.logf(" Preflight aborted: origin '%s' not allowed", origin) return } @@ -305,40 +353,41 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { c.logf(" Preflight aborted: method '%s' not allowed", reqMethod) return } - // Amazon API Gateway is sometimes feeding multiple values for - // Access-Control-Request-Headers in a way where r.Header.Values() picks - // them all up, but r.Header.Get() does not. - // I suspect it is something like this: https://stackoverflow.com/a/4371395 - reqHeaderList := strings.Join(r.Header.Values("Access-Control-Request-Headers"), ",") - reqHeaders := parseHeaderList(reqHeaderList) + reqHeadersRaw := r.Header["Access-Control-Request-Headers"] + reqHeaders, reqHeadersEdited := convertDidCopy(splitHeaderValues(reqHeadersRaw), http.CanonicalHeaderKey) if !c.areHeadersAllowed(reqHeaders) { c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders) return } if c.allowedOriginsAll { - headers.Set("Access-Control-Allow-Origin", "*") + headers["Access-Control-Allow-Origin"] = headerOriginAll } else { - headers.Set("Access-Control-Allow-Origin", origin) + headers["Access-Control-Allow-Origin"] = r.Header["Origin"] } // Spec says: Since the list of methods can be unbounded, simply returning the method indicated // by Access-Control-Request-Method (if supported) can be enough - headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod)) + headers["Access-Control-Allow-Methods"] = r.Header["Access-Control-Request-Method"] if len(reqHeaders) > 0 { - // Spec says: Since the list of headers can be unbounded, simply returning supported headers // from Access-Control-Request-Headers can be enough - headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) + if reqHeadersEdited || len(reqHeaders) != len(reqHeadersRaw) { + headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) + } else { + headers["Access-Control-Allow-Headers"] = reqHeadersRaw + } } if c.allowCredentials { - headers.Set("Access-Control-Allow-Credentials", "true") + headers["Access-Control-Allow-Credentials"] = headerTrue } if c.allowPrivateNetwork && r.Header.Get("Access-Control-Request-Private-Network") == "true" { - headers.Set("Access-Control-Allow-Private-Network", "true") + headers["Access-Control-Allow-Private-Network"] = headerTrue } - if c.maxAge > 0 { - headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge)) + if len(c.maxAge) > 0 { + headers["Access-Control-Max-Age"] = c.maxAge + } + if c.Log != nil { + c.logf(" Preflight response headers: %v", headers) } - c.logf(" Preflight response headers: %v", headers) } // handleActualRequest handles simple cross-origin requests, actual request or redirects @@ -346,13 +395,22 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { headers := w.Header() origin := r.Header.Get("Origin") + allowed, additionalVaryHeaders := c.isOriginAllowed(r, origin) + // Always set Vary, see https://github.com/rs/cors/issues/10 - headers.Add("Vary", "Origin") + if vary, found := headers["Vary"]; found { + headers["Vary"] = append(vary, headerVaryOrigin[0]) + } else { + headers["Vary"] = headerVaryOrigin + } + if len(additionalVaryHeaders) > 0 { + headers.Add("Vary", strings.Join(convert(additionalVaryHeaders, http.CanonicalHeaderKey), ", ")) + } if origin == "" { c.logf(" Actual request no headers added: missing origin") return } - if !c.isOriginAllowed(r, origin) { + if !allowed { c.logf(" Actual request no headers added: origin '%s' not allowed", origin) return } @@ -363,21 +421,22 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { // We think it's a nice feature to be able to have control on those methods though. if !c.isMethodAllowed(r.Method) { c.logf(" Actual request no headers added: method '%s' not allowed", r.Method) - return } if c.allowedOriginsAll { - headers.Set("Access-Control-Allow-Origin", "*") + headers["Access-Control-Allow-Origin"] = headerOriginAll } else { - headers.Set("Access-Control-Allow-Origin", origin) + headers["Access-Control-Allow-Origin"] = r.Header["Origin"] } if len(c.exposedHeaders) > 0 { - headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", ")) + headers["Access-Control-Expose-Headers"] = c.exposedHeaders } if c.allowCredentials { - headers.Set("Access-Control-Allow-Credentials", "true") + headers["Access-Control-Allow-Credentials"] = headerTrue + } + if c.Log != nil { + c.logf(" Actual response added headers: %v", headers) } - c.logf(" Actual response added headers: %v", headers) } // convenience method. checks if a logger is set. @@ -390,33 +449,31 @@ func (c *Cors) logf(format string, a ...interface{}) { // check the Origin of a request. No origin at all is also allowed. func (c *Cors) OriginAllowed(r *http.Request) bool { origin := r.Header.Get("Origin") - return c.isOriginAllowed(r, origin) + allowed, _ := c.isOriginAllowed(r, origin) + return allowed } // isOriginAllowed checks if a given origin is allowed to perform cross-domain requests // on the endpoint -func (c *Cors) isOriginAllowed(r *http.Request, origin string) bool { - if c.allowOriginRequestFunc != nil { - return c.allowOriginRequestFunc(r, origin) - } +func (c *Cors) isOriginAllowed(r *http.Request, origin string) (allowed bool, varyHeaders []string) { if c.allowOriginFunc != nil { - return c.allowOriginFunc(origin) + return c.allowOriginFunc(r, origin) } if c.allowedOriginsAll { - return true + return true, nil } origin = strings.ToLower(origin) for _, o := range c.allowedOrigins { if o == origin { - return true + return true, nil } } for _, w := range c.allowedWOrigins { if w.match(origin) { - return true + return true, nil } } - return false + return false, nil } // isMethodAllowed checks if a given method can be used as part of a cross-domain request @@ -426,7 +483,6 @@ func (c *Cors) isMethodAllowed(method string) bool { // If no method allowed, always return false, even for preflight request return false } - method = strings.ToUpper(method) if method == http.MethodOptions { // Always allow preflight requests return true @@ -446,7 +502,6 @@ func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool { return true } for _, header := range requestedHeaders { - header = http.CanonicalHeaderKey(header) found := false for _, h := range c.allowedHeaders { if h == header { diff --git a/vendor/github.com/rs/cors/utils.go b/vendor/github.com/rs/cors/utils.go index 6bb120caed..ca9983d3f7 100644 --- a/vendor/github.com/rs/cors/utils.go +++ b/vendor/github.com/rs/cors/utils.go @@ -1,8 +1,8 @@ package cors -import "strings" - -const toLower = 'a' - 'A' +import ( + "strings" +) type converter func(string) string @@ -15,57 +15,58 @@ func (w wildcard) match(s string) bool { return len(s) >= len(w.prefix)+len(w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) } -// convert converts a list of string using the passed converter function -func convert(s []string, c converter) []string { - out := []string{} - for _, i := range s { - out = append(out, c(i)) - } - return out -} - -// parseHeaderList tokenize + normalize a string containing a list of headers -func parseHeaderList(headerList string) []string { - l := len(headerList) - h := make([]byte, 0, l) - upper := true - // Estimate the number headers in order to allocate the right splice size - t := 0 - for i := 0; i < l; i++ { - if headerList[i] == ',' { - t++ - } - } - headers := make([]string, 0, t) - for i := 0; i < l; i++ { - b := headerList[i] - switch { - case b >= 'a' && b <= 'z': - if upper { - h = append(h, b-toLower) - } else { - h = append(h, b) +// split compounded header values ["foo, bar", "baz"] -> ["foo", "bar", "baz"] +func splitHeaderValues(values []string) []string { + out := values + copied := false + for i, v := range values { + needsSplit := strings.IndexByte(v, ',') != -1 + if !copied { + if needsSplit { + split := strings.Split(v, ",") + out = make([]string, i, len(values)+len(split)-1) + copy(out, values[:i]) + for _, s := range split { + out = append(out, strings.TrimSpace(s)) + } + copied = true } - case b >= 'A' && b <= 'Z': - if !upper { - h = append(h, b+toLower) + } else { + if needsSplit { + split := strings.Split(v, ",") + for _, s := range split { + out = append(out, strings.TrimSpace(s)) + } } else { - h = append(h, b) + out = append(out, v) } - case b == '-' || b == '_' || b == '.' || (b >= '0' && b <= '9'): - h = append(h, b) } + } + return out +} + +// convert converts a list of string using the passed converter function +func convert(s []string, c converter) []string { + out, _ := convertDidCopy(s, c) + return out +} - if b == ' ' || b == ',' || i == l-1 { - if len(h) > 0 { - // Flush the found header - headers = append(headers, string(h)) - h = h[:0] - upper = true +// convertDidCopy is same as convert but returns true if it copied the slice +func convertDidCopy(s []string, c converter) ([]string, bool) { + out := s + copied := false + for i, v := range s { + if !copied { + v2 := c(v) + if v2 != v { + out = make([]string, len(s)) + copy(out, s[:i]) + out[i] = v2 + copied = true } } else { - upper = b == '-' || b == '_' + out[i] = c(v) } } - return headers + return out, copied } diff --git a/vendor/golang.org/x/net/http2/Dockerfile b/vendor/golang.org/x/net/http2/Dockerfile deleted file mode 100644 index 8512245952..0000000000 --- a/vendor/golang.org/x/net/http2/Dockerfile +++ /dev/null @@ -1,51 +0,0 @@ -# -# This Dockerfile builds a recent curl with HTTP/2 client support, using -# a recent nghttp2 build. -# -# See the Makefile for how to tag it. If Docker and that image is found, the -# Go tests use this curl binary for integration tests. -# - -FROM ubuntu:trusty - -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get install -y git-core build-essential wget - -RUN apt-get install -y --no-install-recommends \ - autotools-dev libtool pkg-config zlib1g-dev \ - libcunit1-dev libssl-dev libxml2-dev libevent-dev \ - automake autoconf - -# The list of packages nghttp2 recommends for h2load: -RUN apt-get install -y --no-install-recommends make binutils \ - autoconf automake autotools-dev \ - libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev \ - libev-dev libevent-dev libjansson-dev libjemalloc-dev \ - cython python3.4-dev python-setuptools - -# Note: setting NGHTTP2_VER before the git clone, so an old git clone isn't cached: -ENV NGHTTP2_VER 895da9a -RUN cd /root && git clone https://github.com/tatsuhiro-t/nghttp2.git - -WORKDIR /root/nghttp2 -RUN git reset --hard $NGHTTP2_VER -RUN autoreconf -i -RUN automake -RUN autoconf -RUN ./configure -RUN make -RUN make install - -WORKDIR /root -RUN wget https://curl.se/download/curl-7.45.0.tar.gz -RUN tar -zxvf curl-7.45.0.tar.gz -WORKDIR /root/curl-7.45.0 -RUN ./configure --with-ssl --with-nghttp2=/usr/local -RUN make -RUN make install -RUN ldconfig - -CMD ["-h"] -ENTRYPOINT ["/usr/local/bin/curl"] - diff --git a/vendor/golang.org/x/net/http2/Makefile b/vendor/golang.org/x/net/http2/Makefile deleted file mode 100644 index 55fd826f77..0000000000 --- a/vendor/golang.org/x/net/http2/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -curlimage: - docker build -t gohttp2/curl . - diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index 033b6e6db6..6d5e008874 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -1012,14 +1012,6 @@ func (sc *serverConn) serve() { } } -func (sc *serverConn) awaitGracefulShutdown(sharedCh <-chan struct{}, privateCh chan struct{}) { - select { - case <-sc.doneServing: - case <-sharedCh: - close(privateCh) - } -} - type serverMessage int // Message values sent to serveMsgCh. diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index b0d482f9f4..4515b22c4a 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -291,8 +291,7 @@ func (t *Transport) initConnPool() { // HTTP/2 server. type ClientConn struct { t *Transport - tconn net.Conn // usually *tls.Conn, except specialized impls - tconnClosed bool + tconn net.Conn // usually *tls.Conn, except specialized impls tlsState *tls.ConnectionState // nil only for specialized impls reused uint32 // whether conn is being reused; atomic singleUse bool // whether being used for a single http.Request diff --git a/vendor/golang.org/x/net/publicsuffix/data/children b/vendor/golang.org/x/net/publicsuffix/data/children index 1038c561ade4683e91b37e8298fc5ead7c306779..08261bffd196fd6942b4cebb5ff06e0ffe53808d 100644 GIT binary patch literal 2976 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 literal 2876 zcmWO8`CAib0svrUzRb+`nSk6zK|{VtNTRiJd7yyG8JU16mRliLD^_b=t;%||>na8l zP!yF%fdB@t+Ui#Avu#}!)CeZrS5%ZMplz*Iv9&5~*B{>h;dOCwadCeq;GIS9q`Z^& z4zVS!huE^`0kP%QL!#hTKe0dV5pkd}k|?!C6Nl&oqRgryj?$?_d0`G=rZt2)emhZX z-9en7jfADpL|Ci`i8}faai*}0IAc9YoTX2R&&HotpS7M5e;NNJao+kBaiQ><_=2^8 zxJch1F2>u5ONGtEC2I%qt+kW*&U&Bt!TN}}690_2YJE;zx4sqEGeBIQz$5DSQiP46 z346kOMDyNYqMeyTbTF|*XM&RGx}8MyGpQt*u$=@DVW1RXU~nZTDBVa8s31KJv7}dH zBIym6lHSS`(z|gP>8ng7eGQqUP?<$eHK@s{jhpc_xP=T*Zp8tHe~|%=yGSwoHz`r> z)<_JcSPBnfsj`ez7!GR`jVH+&@Dv%`cn*ia+c-qoh(iobI27K&p-MXrH8hiA?+&y|}@M@D2V1e1j9<8#Y&blbeWd+C1_t-LV zFPDvbjVnn9e-(BZ^RUCF!FO$1e2@DG-!tapd$u+BKKC)cZ(N7__@9t{+^1xpZ3BK_ z+^BiTZN?961>`V)8y?}C@Ca9iM~sK@DE|l^HJ0O1+cErze;hwDR^UgrD*Tvl#*evb z^0Bc7|IF3mpN(JPpKV{`C;ao|6Yc_jV*C$&V*3XF!oP@r;V$8){LA<$_h0g<@jLPv z|9kS8?F#uTca40(uP4WhjpT3q7V>v~7x}x*LB6)#C*N?7@EhZgCI~j&0$~Cx8>VVw!|d%~wxAQtHFbe- z!9%dvKG>A@uAl4OuxMFt@*X#=tTqgl#u|G&m!hma509ElUken0(Qe4A9O41^* zym>KL(aeFg;#82@KJFwSYKQPHo9H~8wqhMdMH`rIA02L+E*-E#6u$9T1*vgX6*vgj8Y?a#< zwkmlmTUAoPR<-;SnBBGkbMki9T(X0$F4@V}xb0$VN_Mj~Ero1t@?N&Kq=>C;*#|7i zMsTvE6r3(O#&d6}m3X+vNIX(vB_0RjBpz+?JkPcSo_8C^Qyoait;Ej8= z@c!=nmEv{&O$k=uMdO=r+-qkyl^6me1$6X8Kq1|gj8iuh`!2qi@qvt zD`oL5pw9FhpuXujMSXw7MqTasiE8$JOSPqkQ9bF4sRu_hsnJQBsh=j5Q_m-z);~|b zNsS%7MUC~gP`~%KQhyx1PmT8uQGfQ1QGchuqUqj0X(q#uM#8D|1fhf$2<3r-j3C<0 z5ll}ME}$otN6_w$DB80;hV~LB(q0Y~?JZnNPjaNtLSZgFIU|qubLeURjP$^7w=>?-ckWN6n~%?+Tm9zH?b#7@ zXLcOjdpwDD_^k?bWakAsj;rarej55OKV9Ho*?$E7b^JBslKn>JQb8~-eI!HV02%2| z$$&qUfeL|)m*d9pDm-MoK2I5)<0Yf}Cd-%{KN(XoRR;a1$zVk!2=9l1)T!@NY+X-;H1`;(b2(Nd->H-+gk zFOzlkFK4<%sZ4k73Z~oq0n^=|ChN&fXL`(;OizCn(<{oB_2%X<3z;Ev5i^{-j~O->Ll;qr{M`oRE(3&|2mo>-j|YhX z3QnwM$KsKCQt%ZKoA40!@ zPoRImdMK*?6sq!e!VGaR%uwgSj6pTb5^G_WdNo{GlMmJ6&2qJRH`I#vKz)q~>IaX& zj|Pvz{2DX-c<>}#J9r+h6JLbu)R*7}@nyJS@Fv`(zAfKW(++pmbjWuOzJZ^M-@-4% zT<`gC~Y|LJQsx>of=8Da~Pc23Nu}8Vfv&>)<(j8lJ}&;Q65|@XF9N z_&+=buWDYxtF^D;b^Hci*Sf%ZmVoucJlMc8u;B!R4Z{=Q4VDjYX$7!}^?^-V3AAaY zuw{5AY#&}A?_d*PhgJzYhL=ExmV=HHYUmi&z`Lv#KG5pmL+xj9h%JCaS_2%>7Qr{{ zVK}aRj7au5;yIy$(s?N;i;seG`Xbsn2|=A7nqm5d_}q!P)U)ktAE zfu`$Dq8XAiXom9~de3qm&D4E^f+Uwwkn=hUw%kA=Ix7m5G@($Z9fj#y(QHXOn(gdD zb1c1RuI?V1CwYM8IR{X2h9V^|P^9xEiqgG83nj17LgzSI zWceE{){`h&N}=cxh+^vaC|2)=V(UCmoZb_~kNBVjsUK2G{ZXPc043_>XsKR-mexh0 zl#wX3LK=h8q$wy(pMlawGEurT8)fQqP?lbgvPbgKO8t7YYUDGd)^9_ax;;oI-G_Ag Z1B%rnr6^x|0)0GUL2F0Oqfew4{}2BS2s{7) diff --git a/vendor/golang.org/x/net/publicsuffix/data/nodes b/vendor/golang.org/x/net/publicsuffix/data/nodes index 34751cd5b9d1b14c3a0977e4fa4b04db058b241d..1dae6ede8f292889cb4252aa473312fea1bac46a 100644 GIT binary patch literal 46610 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

kM-i?FIu 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 diff --git a/vendor/golang.org/x/net/publicsuffix/data/text b/vendor/golang.org/x/net/publicsuffix/data/text index 124dcd61f4..7e516413f6 100644 --- a/vendor/golang.org/x/net/publicsuffix/data/text +++ b/vendor/golang.org/x/net/publicsuffix/data/text @@ -1 +1 @@ -billustrationionjukudoyamakeupowiathletajimageandsoundandvision-riopretobishimagentositecnologiabiocelotenkawabipanasonicatfoodnetworkinggroupperbirdartcenterprisecloudaccesscamdvrcampaniabirkenesoddtangenovarahkkeravjuegoshikikiraraholtalenishikatakazakindependent-revieweirbirthplaceu-1bitbucketrzynishikatsuragirlyuzawabitternidiscoverybjarkoybjerkreimdbaltimore-og-romsdalp1bjugnishikawazukamishihoronobeautydalwaysdatabaseballangenkainanaejrietisalatinabenogatabitorderblackfridaybloombergbauernishimerabloxcms3-website-us-west-2blushakotanishinomiyashironocparachutingjovikarateu-2bmoattachmentsalangenishinoomotegovtattoolforgerockartuzybmsalon-1bmwellbeingzoneu-3bnrwesteuropenairbusantiquesaltdalomzaporizhzhedmarkaratsuginamikatagamilanotairesistanceu-4bondigitaloceanspacesaludishangrilanciabonnishinoshimatsusakahoginankokubunjindianapolis-a-bloggerbookonlinewjerseyboomlahppiacenzachpomorskienishiokoppegardiskussionsbereichattanooganordkapparaglidinglassassinationalheritageu-north-1boschaefflerdalondonetskarelianceu-south-1bostik-serveronagasukevje-og-hornnesalvadordalibabalatinord-aurdalipaywhirlondrinaplesknsalzburgleezextraspace-to-rentalstomakomaibarabostonakijinsekikogentappssejnyaarparalleluxembourglitcheltenham-radio-opensocialorenskogliwicebotanicalgardeno-staginglobodoes-itcouldbeworldisrechtranakamurataiwanairforcechireadthedocsxeroxfinitybotanicgardenishitosashimizunaminamiawajikindianmarketinglogowestfalenishiwakindielddanuorrindigenamsskoganeindustriabotanyanagawallonieruchomoscienceandindustrynissandiegoddabouncemerckmsdnipropetrovskjervoyageorgeorgiabounty-fullensakerrypropertiesamegawaboutiquebecommerce-shopselectaxihuanissayokkaichintaifun-dnsaliasamnangerboutireservditchyouriparasiteboyfriendoftheinternetflixjavaldaostathellevangerbozen-sudtirolottokorozawabozen-suedtirolouvreisenissedalovepoparisor-fronisshingucciprianiigataipeidsvollovesickariyakumodumeloyalistoragebplaceducatorprojectcmembersampalermomahaccapooguybrandywinevalleybrasiliadboxosascoli-picenorddalpusercontentcp4bresciaokinawashirosatobamagazineuesamsclubartowestus2brindisibenikitagataikikuchikumagayagawalmartgorybristoloseyouriparliamentjeldsundivtasvuodnakaniikawatanagurabritishcolumbialowiezaganiyodogawabroadcastlebtimnetzlgloomy-routerbroadwaybroke-itvedestrandivttasvuotnakanojohanamakindlefrakkestadiybrokerbrothermesaverdeatnulmemergencyachtsamsungloppennebrowsersafetymarketsandnessjoenl-ams-1brumunddalublindesnesandoybrunelastxn--0trq7p7nnbrusselsandvikcoromantovalle-daostavangerbruxellesanfranciscofreakunekobayashikaoirmemorialucaniabryanskodjedugit-pagespeedmobilizeroticagliaricoharuovatlassian-dev-builderscbglugsjcbnpparibashkiriabrynewmexicoacharterbuzzwfarmerseinebwhalingmbhartiffany-2bzhitomirbzzcodyn-vpndnsantacruzsantafedjeffersoncoffeedbackdropocznordlandrudupontariobranconavstackasaokamikoaniikappudownloadurbanamexhibitioncogretakamatsukawacollectioncolognewyorkshirebungoonordre-landurhamburgrimstadynamisches-dnsantamariakecolonialwilliamsburgripeeweeklylotterycoloradoplateaudnedalncolumbusheycommunexus-3community-prochowicecomobaravendbambleborkapsicilyonagoyauthgear-stagingivestbyglandroverhallair-traffic-controlleyombomloabaths-heilbronnoysunddnslivegarsheiheijibigawaustraliaustinnfshostrolekamisatokaizukameyamatotakadaustevollivornowtv-infolldalolipopmcdircompanychipstmncomparemarkerryhotelsantoandrepbodynaliasnesoddenmarkhangelskjakdnepropetrovskiervaapsteigenflfannefrankfurtjxn--12cfi8ixb8lutskashibatakashimarshallstatebankashiharacomsecaaskimitsubatamibuildingriwatarailwaycondoshichinohealth-carereformemsettlersanukindustriesteamfamberlevagangaviikanonjinfinitigotembaixadaconferenceconstructionconsuladogadollsaobernardomniweatherchanneluxuryconsultanthropologyconsultingroks-thisayamanobeokakegawacontactkmaxxn--12co0c3b4evalled-aostamayukinsuregruhostingrondarcontagematsubaravennaharimalborkashiwaracontemporaryarteducationalchikugodonnakaiwamizawashtenawsmppl-wawdev-myqnapcloudcontrolledogawarabikomaezakirunoopschlesischesaogoncartoonartdecologiacontractorskenconventureshinodearthickashiwazakiyosatokamachilloutsystemscloudsitecookingchannelsdvrdnsdojogaszkolancashirecifedexetercoolblogdnsfor-better-thanawassamukawatarikuzentakatairavpagecooperativano-frankivskygearapparochernigovernmentksatxn--1ck2e1bananarepublic-inquiryggeebinatsukigatajimidsundevelopmentatarantours3-external-1copenhagencyclopedichiropracticatholicaxiashorokanaiecoproductionsaotomeinforumzcorporationcorsicahcesuoloanswatch-and-clockercorvettenrissagaeroclubmedecincinnativeamericanantiquest-le-patron-k3sapporomuracosenzamamidorittoeigersundynathomebuiltwithdarkasserverrankoshigayaltakasugaintelligencecosidnshome-webservercellikescandypoppdaluzerncostumedicallynxn--1ctwolominamatargets-itlon-2couchpotatofriesardegnarutomobegetmyiparsardiniacouncilvivanovoldacouponsarlcozoracq-acranbrookuwanalyticsarpsborgrongausdalcrankyowariasahikawatchandclockasukabeauxartsandcraftsarufutsunomiyawakasaikaitabashijonawatecrdyndns-at-homedepotaruinterhostsolutionsasayamatta-varjjatmpartinternationalfirearmsaseboknowsitallcreditcardyndns-at-workshoppingrossetouchigasakitahiroshimansionsaskatchewancreditunioncremonashgabadaddjaguarqcxn--1lqs03ncrewhmessinarashinomutashinaintuitoyosatoyokawacricketnedalcrimeast-kazakhstanangercrotonecrownipartsassarinuyamashinazawacrsaudacruisesauheradyndns-blogsitextilegnicapetownnews-stagingroundhandlingroznycuisinellancasterculturalcentertainmentoyotapartysvardocuneocupcakecuritibabymilk3curvallee-d-aosteinkjerusalempresashibetsurugashimaringatlantajirinvestmentsavannahgacutegirlfriendyndns-freeboxoslocalzonecymrulvikasumigaurawa-mazowszexnetlifyinzairtrafficplexus-1cyonabarumesswithdnsaveincloudyndns-homednsaves-the-whalessandria-trani-barletta-andriatranibarlettaandriacyouthruherecipescaracaltanissettaishinomakilovecollegefantasyleaguernseyfembetsukumiyamazonawsglobalacceleratorahimeshimabaridagawatchesciencecentersciencehistoryfermockasuyamegurownproviderferraraferraris-a-catererferrerotikagoshimalopolskanlandyndns-picsaxofetsundyndns-remotewdyndns-ipasadenaroyfgujoinvilleitungsenfhvalerfidontexistmein-iservschulegallocalhostrodawarafieldyndns-serverdalfigueresindevicenzaolkuszczytnoipirangalsaceofilateliafilegear-augustowhoswholdingsmall-webthingscientistordalfilegear-debianfilegear-gbizfilegear-iefilegear-jpmorganfilegear-sg-1filminamiechizenfinalfinancefineartscrapper-sitefinlandyndns-weblikes-piedmonticellocus-4finnoyfirebaseappaviancarrdyndns-wikinkobearalvahkijoetsuldalvdalaskanittedallasalleasecuritytacticschoenbrunnfirenetoystre-slidrettozawafirenzefirestonefirewebpaascrappingulenfirmdaleikangerfishingoldpoint2thisamitsukefitjarvodkafjordyndns-workangerfitnessettlementozsdellogliastradingunmanxn--1qqw23afjalerfldrvalleeaosteflekkefjordyndns1flesberguovdageaidnunjargaflickragerogerscrysecretrosnubar0flierneflirfloginlinefloppythonanywhereggio-calabriafloraflorencefloridatsunangojomedicinakamagayahabackplaneapplinzis-a-celticsfanfloripadoval-daostavalleyfloristanohatakahamalselvendrellflorokunohealthcareerscwienflowerservehalflifeinsurancefltrani-andria-barletta-trani-andriaflynnhosting-clusterfnchiryukyuragifuchungbukharanzanfndynnschokokekschokoladenfnwkaszubytemarkatowicefoolfor-ourfor-somedio-campidano-mediocampidanomediofor-theaterforexrothachijolsterforgotdnservehttpbin-butterforli-cesena-forlicesenaforlillesandefjordynservebbscholarshipschoolbusinessebyforsaleirfjordynuniversityforsandasuolodingenfortalfortefortmissoulangevagrigentomologyeonggiehtavuoatnagahamaroygardencowayfortworthachinoheavyfosneservehumourfotraniandriabarlettatraniandriafoxfordecampobassociatest-iserveblogsytemp-dnserveirchitachinakagawashingtondchernivtsiciliafozfr-par-1fr-par-2franamizuhobby-sitefrancaiseharafranziskanerimalvikatsushikabedzin-addrammenuorochesterfredrikstadtvserveminecraftranoyfreeddnsfreebox-oservemp3freedesktopfizerfreemasonryfreemyiphosteurovisionfreesitefreetlservep2pgfoggiafreiburgushikamifuranorfolkebibleksvikatsuyamarugame-hostyhostingxn--2m4a15efrenchkisshikirkeneservepicservequakefreseniuscultureggio-emilia-romagnakasatsunairguardiannakadomarinebraskaunicommbankaufentigerfribourgfriuli-v-giuliafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganservesarcasmatartanddesignfrognfrolandynv6from-akrehamnfrom-alfrom-arfrom-azurewebsiteshikagamiishibukawakepnoorfrom-capitalonewportransipharmacienservicesevastopolefrom-coalfrom-ctranslatedynvpnpluscountryestateofdelawareclaimschoolsztynsettsupportoyotomiyazakis-a-candidatefrom-dchitosetodayfrom-dediboxafrom-flandersevenassisienarvikautokeinoticeablewismillerfrom-gaulardalfrom-hichisochikuzenfrom-iafrom-idyroyrvikingruenoharafrom-ilfrom-in-berlindasewiiheyaizuwakamatsubushikusakadogawafrom-ksharpharmacyshawaiijimarcheapartmentshellaspeziafrom-kyfrom-lanshimokawafrom-mamurogawatsonfrom-mdfrom-medizinhistorischeshimokitayamattelekommunikationfrom-mifunefrom-mnfrom-modalenfrom-mshimonitayanagit-reposts-and-telecommunicationshimonosekikawafrom-mtnfrom-nchofunatoriginstantcloudfrontdoorfrom-ndfrom-nefrom-nhktistoryfrom-njshimosuwalkis-a-chefarsundyndns-mailfrom-nminamifuranofrom-nvalleedaostefrom-nynysagamiharafrom-ohdattorelayfrom-oketogolffanshimotsukefrom-orfrom-padualstackazoologicalfrom-pratogurafrom-ris-a-conservativegashimotsumayfirstockholmestrandfrom-schmidtre-gauldalfrom-sdscloudfrom-tnfrom-txn--2scrj9chonanbunkyonanaoshimakanegasakikugawaltervistailscaleforcefrom-utsiracusaikirovogradoyfrom-vald-aostarostwodzislawildlifestylefrom-vtransportefrom-wafrom-wiardwebview-assetshinichinanfrom-wvanylvenneslaskerrylogisticshinjournalismartlabelingfrom-wyfrosinonefrostalowa-wolawafroyal-commissionfruskydivingfujiiderafujikawaguchikonefujiminokamoenairkitapps-auction-rancherkasydneyfujinomiyadattowebhoptogakushimotoganefujiokayamandalfujisatoshonairlinedre-eikerfujisawafujishiroishidakabiratoridedyn-berlincolnfujitsuruokazakiryuohkurafujiyoshidavvenjargap-east-1fukayabeardubaiduckdnsncfdfukuchiyamadavvesiidappnodebalancertmgrazimutheworkpccwilliamhillfukudomigawafukuis-a-cpalacefukumitsubishigakisarazure-mobileirvikazteleportlligatransurlfukuokakamigaharafukuroishikarikaturindalfukusakishiwadazaifudaigokaseljordfukuyamagatakaharunusualpersonfunabashiriuchinadafunagatakahashimamakisofukushimangonnakatombetsumy-gatewayfunahashikamiamakusatsumasendaisenergyfundaciofunkfeuerfuoiskujukuriyamangyshlakasamatsudoomdnstracefuosskoczowinbar1furubirafurudonostiaafurukawajimaniwakuratefusodegaurafussaintlouis-a-anarchistoireggiocalabriafutabayamaguchinomihachimanagementrapaniizafutboldlygoingnowhere-for-morenakatsugawafuttsurutaharafuturecmshinjukumamotoyamashikefuturehostingfuturemailingfvghamurakamigoris-a-designerhandcraftedhandsonyhangglidinghangoutwentehannanmokuizumodenaklodzkochikuseihidorahannorthwesternmutualhanyuzenhapmircloudletshintokushimahappounzenharvestcelebrationhasamap-northeast-3hasaminami-alpshintomikasaharahashbangryhasudahasura-apphiladelphiaareadmyblogspotrdhasvikfh-muensterhatogayahoooshikamaishimofusartshinyoshitomiokamisunagawahatoyamazakitakatakanabeatshiojirishirifujiedahatsukaichikaiseiyoichimkentrendhostinghattfjelldalhayashimamotobusellfylkesbiblackbaudcdn-edgestackhero-networkisboringhazuminobushistoryhelplfinancialhelsinkitakyushuaiahembygdsforbundhemneshioyanaizuerichardlimanowarudahemsedalhepforgeblockshirahamatonbetsurgeonshalloffameiwamasoyheroyhetemlbfanhgtvaohigashiagatsumagoianiahigashichichibuskerudhigashihiroshimanehigashiizumozakitamigrationhigashikagawahigashikagurasoedahigashikawakitaaikitamotosunndalhigashikurumeeresinstaginghigashimatsushimarburghigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshirakokonoehigashinarusells-for-lesshiranukamitondabayashiogamagoriziahigashinehigashiomitamanortonsberghigashiosakasayamanakakogawahigashishirakawamatakanezawahigashisumiyoshikawaminamiaikitanakagusukumodernhigashitsunosegawahigashiurausukitashiobarahigashiyamatokoriyamanashifteditorxn--30rr7yhigashiyodogawahigashiyoshinogaris-a-doctorhippyhiraizumisatohnoshoohirakatashinagawahiranairportland-4-salernogiessennanjobojis-a-financialadvisor-aurdalhirarahiratsukaerusrcfastlylbanzaicloudappspotagerhirayaitakaokalmykiahistorichouseshiraois-a-geekhakassiahitachiomiyagildeskaliszhitachiotagonohejis-a-greenhitraeumtgeradegreehjartdalhjelmelandholeckodairaholidayholyhomegoodshiraokamitsuehomeiphilatelyhomelinkyard-cloudjiffyresdalhomelinuxn--32vp30hachiojiyahikobierzycehomeofficehomesecuritymacaparecidahomesecuritypchoseikarugamvikarlsoyhomesenseeringhomesklepphilipsynology-diskstationhomeunixn--3bst00minamiiserniahondahongooglecodebergentinghonjyoitakarazukaluganskharkivaporcloudhornindalhorsells-for-ustkanmakiwielunnerhortendofinternet-dnshiratakahagitapphoenixn--3ds443ghospitalhoteleshishikuis-a-guruhotelwithflightshisognehotmailhoyangerhoylandetakasagophonefosshisuifuettertdasnetzhumanitieshitaramahungryhurdalhurumajis-a-hard-workershizukuishimogosenhyllestadhyogoris-a-hunterhyugawarahyundaiwafuneis-into-carsiiitesilkharkovaresearchaeologicalvinklein-the-bandairtelebitbridgestoneenebakkeshibechambagricultureadymadealstahaugesunderseaportsinfolionetworkdalaheadjudygarlandis-into-cartoonsimple-urlis-into-gamesserlillyis-leetrentin-suedtirolis-lostre-toteneis-a-lawyeris-not-certifiedis-savedis-slickhersonis-uberleetrentino-a-adigeis-very-badajozis-a-liberalis-very-evillageis-very-goodyearis-very-niceis-very-sweetpepperugiais-with-thebandovre-eikerisleofmanaustdaljellybeanjenv-arubahccavuotnagaragusabaerobaticketsirdaljeonnamerikawauejetztrentino-aadigejevnakershusdecorativeartslupskhmelnytskyivarggatrentino-alto-adigejewelryjewishartgalleryjfkhplaystation-cloudyclusterjgorajlljls-sto1jls-sto2jls-sto3jmphotographysiojnjaworznospamproxyjoyentrentino-altoadigejoyokaichibajddarchitecturealtorlandjpnjprslzjurkotohiradomainstitutekotourakouhokutamamurakounosupabasembokukizunokunimilitarykouyamarylhurstjordalshalsenkouzushimasfjordenkozagawakozakis-a-llamarnardalkozowindowskrakowinnersnoasakatakkokamiminersokndalkpnkppspbarcelonagawakkanaibetsubamericanfamilyds3-fips-us-gov-west-1krasnikahokutokashikis-a-musiciankrasnodarkredstonekrelliankristiansandcatsolarssonkristiansundkrodsheradkrokstadelvalle-aostatic-accessolognekryminamiizukaminokawanishiaizubangekumanotteroykumatorinovecoregontrailroadkumejimashikis-a-nascarfankumenantokonamegatakatoris-a-nursells-itrentin-sud-tirolkunisakis-a-painteractivelvetrentin-sudtirolkunitachiaraindropilotsolundbecknx-serversellsyourhomeftphxn--3e0b707ekunitomigusukuleuvenetokigawakunneppuboliviajessheimpertrixcdn77-secureggioemiliaromagnamsosnowiechristiansburgminakamichiharakunstsammlungkunstunddesignkuokgroupimientaketomisatoolsomakurehabmerkurgankurobeeldengeluidkurogimimatakatsukis-a-patsfankuroisoftwarezzoologykuromatsunais-a-personaltrainerkuronkurotakikawasakis-a-photographerokussldkushirogawakustanais-a-playershiftcryptonomichigangwonkusupersalezajskomakiyosemitekutchanelkutnowruzhgorodeokuzumakis-a-republicanonoichinomiyakekvafjordkvalsundkvamscompute-1kvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspdnsomnatalkzmisakis-a-soxfanmisasaguris-a-studentalmisawamisconfusedmishimasudamissilemisugitokuyamatsumaebashikshacknetrentino-sued-tirolmitakeharamitourismilemitoyoakemiuramiyazurecontainerdpolicemiyotamatsukuris-a-teacherkassyno-dshowamjondalenmonstermontrealestatefarmequipmentrentino-suedtirolmonza-brianzapposor-odalmonza-e-della-brianzaptokyotangotpantheonsitemonzabrianzaramonzaebrianzamonzaedellabrianzamoonscalebookinghostedpictetrentinoa-adigemordoviamoriyamatsumotofukemoriyoshiminamiashigaramormonmouthachirogatakamoriokakudamatsuemoroyamatsunomortgagemoscowiosor-varangermoseushimodatemosjoenmoskenesorfoldmossorocabalena-devicesorreisahayakawakamiichikawamisatottoris-a-techietis-a-landscaperspectakasakitchenmosvikomatsushimarylandmoteginowaniihamatamakinoharamoviemovimientolgamozilla-iotrentinoaadigemtranbytomaritimekeepingmuginozawaonsensiositemuikaminoyamaxunispacemukoebenhavnmulhouseoullensvanguardmunakatanemuncienciamuosattemupinbarclaycards3-sa-east-1murmanskomforbar2murotorcraftrentinoalto-adigemusashinoharamuseetrentinoaltoadigemuseumverenigingmusicargodaddyn-o-saurlandesortlandmutsuzawamy-wanggoupilemyactivedirectorymyamazeplaymyasustor-elvdalmycdmycloudnsoruminamimakis-a-rockstarachowicemydattolocalcertificationmyddnsgeekgalaxymydissentrentinos-tirolmydobissmarterthanyoumydrobofageologymydsoundcastronomy-vigorlicemyeffectrentinostirolmyfastly-terrariuminamiminowamyfirewalledreplittlestargardmyforuminamioguni5myfritzmyftpaccessouthcarolinaturalhistorymuseumcentermyhome-servermyjinomykolaivencloud66mymailermymediapchristmasakillucernemyokohamamatsudamypepinkommunalforbundmypetsouthwest1-uslivinghistorymyphotoshibalashovhadanorth-kazakhstanmypicturestaurantrentinosud-tirolmypsxn--3pxu8kommunemysecuritycamerakermyshopblocksowamyshopifymyspreadshopwarendalenugmythic-beastspectruminamisanrikubetsuppliesoomytis-a-bookkeepermaritimodspeedpartnermytuleap-partnersphinxn--41amyvnchromediatechnologymywirepaircraftingvollohmusashimurayamashikokuchuoplantationplantspjelkavikomorotsukagawaplatformsharis-a-therapistoiaplatter-appinokofuefukihaboromskogplatterpioneerplazaplcube-serversicherungplumbingoplurinacionalpodhalepodlasiellaktyubinskiptveterinairealmpmnpodzonepohlpoivronpokerpokrovskomvuxn--3hcrj9choyodobashichikashukujitawaraumalatvuopmicrosoftbankarmoypoliticarrierpolitiendapolkowicepoltavalle-d-aostaticspydebergpomorzeszowitdkongsbergponpesaro-urbino-pesarourbinopesaromasvuotnarusawapordenonepornporsangerporsangugeporsgrunnanyokoshibahikariwanumatakinouepoznanpraxis-a-bruinsfanprdpreservationpresidioprgmrprimetelemarkongsvingerprincipeprivatizehealthinsuranceprofesionalprogressivestfoldpromombetsupplypropertyprotectionprotonetrentinosued-tirolprudentialpruszkowithgoogleapiszprvcyberprzeworskogpulawypunyufuelveruminamiuonumassa-carrara-massacarraramassabuyshousesopotrentino-sud-tirolpupugliapussycateringebuzentsujiiepvhadselfiphdfcbankazunoticiashinkamigototalpvtrentinosuedtirolpwchungnamdalseidsbergmodellingmxn--11b4c3dray-dnsupdaterpzqhaebaruericssongdalenviknakayamaoris-a-cubicle-slavellinodeobjectshinshinotsurfashionstorebaselburguidefinimamateramochizukimobetsumidatlantichirurgiens-dentistes-en-franceqldqotoyohashimotoshimatsuzakis-an-accountantshowtimelbourneqponiatowadaqslgbtrentinsud-tirolqualifioappippueblockbusternopilawaquickconnectrentinsudtirolquicksytesrhtrentinsued-tirolquipelementsrltunestuff-4-saletunkonsulatrobeebyteappigboatsmolaquilanxessmushcdn77-sslingturystykaniepcetuscanytushuissier-justicetuvalleaostaverntuxfamilytwmailvestvagoyvevelstadvibo-valentiavibovalentiavideovillastufftoread-booksnestorfjordvinnicasadelamonedagestangevinnytsiavipsinaappiwatevirginiavirtual-uservecounterstrikevirtualcloudvirtualservervirtualuserveexchangevirtuelvisakuhokksundviterbolognagasakikonaikawagoevivianvivolkenkundenvixn--42c2d9avlaanderennesoyvladikavkazimierz-dolnyvladimirvlogintoyonezawavminanovologdanskonyveloftrentino-stirolvolvolkswagentstuttgartrentinsuedtirolvolyngdalvoorlopervossevangenvotevotingvotoyonovps-hostrowiecircustomer-ocimmobilienwixsitewloclawekoobindalwmcloudwmflabsurnadalwoodsidelmenhorstabackyardsurreyworse-thandawowithyoutuberspacekitagawawpdevcloudwpenginepoweredwphostedmailwpmucdnpixolinodeusercontentrentinosudtirolwpmudevcdnaccessokanagawawritesthisblogoipizzawroclawiwatsukiyonoshiroomgwtcirclerkstagewtfastvps-serverisignwuozuwzmiuwajimaxn--4gbriminingxn--4it168dxn--4it797kooris-a-libertarianxn--4pvxs4allxn--54b7fta0ccivilaviationredumbrellajollamericanexpressexyxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49civilisationrenderxn--5rtq34koperviklabudhabikinokawachinaganoharamcocottempurlxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264civilizationthewifiatmallorcafederation-webspacexn--80aaa0cvacationsusonoxn--80adxhksuzakananiimiharuxn--80ao21axn--80aqecdr1axn--80asehdbarclays3-us-east-2xn--80aswgxn--80aukraanghkembuchikujobservableusercontentrevisohughestripperxn--8dbq2axn--8ltr62koryokamikawanehonbetsuwanouchijiwadeliveryxn--8pvr4uxn--8y0a063axn--90a1affinitylotterybnikeisenbahnxn--90a3academiamicable-modemoneyxn--90aeroportalabamagasakishimabaraffleentry-snowplowiczeladzxn--90aishobarakawaharaoxn--90amckinseyxn--90azhytomyrxn--9dbhblg6dietritonxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byandexcloudxn--asky-iraxn--aurskog-hland-jnbarefootballooningjerstadgcapebretonamicrolightingjesdalombardiadembroideryonagunicloudiherokuappanamasteiermarkaracoldwarszawauthgearappspacehosted-by-previderxn--avery-yuasakuragawaxn--b-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbsuzukanazawaxn--bck1b9a5dre4civilwarmiasadoesntexisteingeekarpaczest-a-la-maisondre-landrayddns5yxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--bjarky-fyaotsurgeryxn--bjddar-ptargithubpreviewsaitohmannore-og-uvdalxn--blt-elabourxn--bmlo-graingerxn--bod-2naturalsciencesnaturellesuzukis-an-actorxn--bozen-sdtirol-2obanazawaxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-acornxn--brum-voagatroandinosaureportrentoyonakagyokutoyakomaganexn--btsfjord-9zaxn--bulsan-sdtirol-nsbaremetalpha-myqnapcloud9guacuiababia-goracleaningitpagexlimoldell-ogliastraderxn--c1avgxn--c2br7gxn--c3s14mincomcastreserve-onlinexn--cck2b3bargainstances3-us-gov-west-1xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-an-actresshwindmillxn--ciqpnxn--clchc0ea0b2g2a9gcdxn--comunicaes-v6a2oxn--correios-e-telecomunicaes-ghc29axn--czr694barreaudiblebesbydgoszczecinemagnethnologyoriikaragandauthordalandroiddnss3-ap-southeast-2ix4432-balsan-suedtirolimiteddnskinggfakefurniturecreationavuotnaritakoelnayorovigotsukisosakitahatakahatakaishimoichinosekigaharaurskog-holandingitlaborxn--czrs0trogstadxn--czru2dxn--czrw28barrel-of-knowledgeappgafanquanpachicappacificurussiautomotivelandds3-ca-central-16-balsan-sudtirollagdenesnaaseinet-freaks3-ap-southeast-123websiteleaf-south-123webseiteckidsmynasushiobarackmazerbaijan-mayen-rootaribeiraogakibichuobiramusementdllpages3-ap-south-123sitewebhareidfjordvagsoyerhcloudd-dnsiskinkyolasiteastcoastaldefenceastus2038xn--d1acj3barrell-of-knowledgecomputerhistoryofscience-fictionfabricafjs3-us-west-1xn--d1alfaromeoxn--d1atromsakegawaxn--d5qv7z876clanbibaidarmeniaxn--davvenjrga-y4axn--djrs72d6uyxn--djty4kosaigawaxn--dnna-grajewolterskluwerxn--drbak-wuaxn--dyry-iraxn--e1a4cldmailukowhitesnow-dnsangohtawaramotoineppubtlsanjotelulubin-brbambinagisobetsuitagajoburgjerdrumcprequalifymein-vigorgebetsukuibmdeveloperauniteroizumizakinderoyomitanobninskanzakiyokawaraustrheimatunduhrennebulsan-suedtirololitapunk123kotisivultrobjectselinogradimo-siemenscaledekaascolipiceno-ipifony-1337xn--eckvdtc9dxn--efvn9svalbardunloppaderbornxn--efvy88hagakhanamigawaxn--ehqz56nxn--elqq16hagebostadxn--eveni-0qa01gaxn--f6qx53axn--fct429kosakaerodromegallupaasdaburxn--fhbeiarnxn--finny-yuaxn--fiq228c5hsvchurchaseljeepsondriodejaneirockyotobetsuliguriaxn--fiq64barsycenterprisesakievennodesadistcgrouplidlugolekagaminord-frontierxn--fiqs8sveioxn--fiqz9svelvikoninjambylxn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbssvizzeraxn--forlcesena-c8axn--fpcrj9c3dxn--frde-grandrapidsvn-repostorjcloud-ver-jpchowderxn--frna-woaraisaijosoyroroswedenxn--frya-hraxn--fzc2c9e2cleverappsannanxn--fzys8d69uvgmailxn--g2xx48clicketcloudcontrolapparmatsuuraxn--gckr3f0fauskedsmokorsetagayaseralingenoamishirasatogliattipschulserverxn--gecrj9clickrisinglesannohekinannestadraydnsanokaruizawaxn--ggaviika-8ya47haibarakitakamiizumisanofidelitysfjordxn--gildeskl-g0axn--givuotna-8yasakaiminatoyookaneyamazoexn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-an-anarchistoricalsocietysnesigdalxn--gmqw5axn--gnstigbestellen-zvbrplsbxn--45br5cylxn--gnstigliefern-wobihirosakikamijimatsushigexn--h-2failxn--h1aeghair-surveillancexn--h1ahnxn--h1alizxn--h2breg3eveneswidnicasacampinagrandebungotakadaemongolianxn--h2brj9c8clinichippubetsuikilatironporterxn--h3cuzk1digickoseis-a-linux-usershoujis-a-knightpointtohoboleslawieconomiastalbanshizuokamogawaxn--hbmer-xqaxn--hcesuolo-7ya35barsyonlinewhampshirealtychyattorneyagawakuyabukihokumakogeniwaizumiotsurugimbalsfjordeportexaskoyabeagleboardetroitskypecorivneatonoshoes3-eu-west-3utilitiesquare7xn--hebda8basicserversaillesjabbottateshinanomachildrensgardenhlfanhsbc66xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-aptibleangaviikadenaamesjevuemielnoboribetsuckswidnikkolobrzegersundxn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyasugithubusercontentromsojamisonxn--io0a7is-an-artistgstagexn--j1adpkomonotogawaxn--j1aefbsbxn--1lqs71dyndns-office-on-the-webhostingrpassagensavonarviikamiokameokamakurazakiwakunigamihamadaxn--j1ael8basilicataniautoscanadaeguambulancentralus-2xn--j1amhakatanorthflankddiamondshinshiroxn--j6w193gxn--jlq480n2rgxn--jlq61u9w7basketballfinanzgorzeleccodespotenzakopanewspaperxn--jlster-byasuokannamihokkaidopaaskvollxn--jrpeland-54axn--jvr189miniserversusakis-a-socialistg-builderxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--45brj9cistrondheimperiaxn--koluokta-7ya57hakodatexn--kprw13dxn--kpry57dxn--kput3is-an-engineeringxn--krager-gyatominamibosogndalxn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdevcloudfunctionsimplesitexn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyatsukanoyakagexn--kvnangen-k0axn--l-1fairwindswiebodzin-dslattuminamiyamashirokawanabeepilepsykkylvenicexn--l1accentureklamborghinikolaeventswinoujscienceandhistoryxn--laheadju-7yatsushiroxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52batochigifts3-us-west-2xn--lesund-huaxn--lgbbat1ad8jdfaststackschulplattformetacentrumeteorappassenger-associationxn--lgrd-poacctrusteexn--lhppi-xqaxn--linds-pramericanartrvestnestudioxn--lns-qlavagiskexn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liacliniquedapliexn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddeswisstpetersburgxn--mgb9awbfbx-ostrowwlkpmguitarschwarzgwangjuifminamidaitomanchesterxn--mgba3a3ejtrycloudflarevistaplestudynamic-dnsrvaroyxn--mgba3a4f16axn--mgba3a4fra1-deloittevaksdalxn--mgba7c0bbn0axn--mgbaakc7dvfstdlibestadxn--mgbaam7a8hakonexn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00batsfjordiscordsays3-website-ap-northeast-1xn--mgbai9azgqp6jejuniperxn--mgbayh7gpalmaseratis-an-entertainerxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mgbpl2fhskosherbrookegawaxn--mgbqly7c0a67fbclintonkotsukubankarumaifarmsteadrobaknoluoktachikawakayamadridvallee-aosteroyxn--mgbqly7cvafr-1xn--mgbt3dhdxn--mgbtf8flapymntrysiljanxn--mgbtx2bauhauspostman-echocolatemasekd1xn--mgbx4cd0abbvieeexn--mix082fbxoschweizxn--mix891fedorainfraclouderaxn--mjndalen-64axn--mk0axin-vpnclothingdustdatadetectjmaxxxn--12c1fe0bradescotlandrrxn--mk1bu44cn-northwest-1xn--mkru45is-bykleclerchoshibuyachiyodancexn--mlatvuopmi-s4axn--mli-tlavangenxn--mlselv-iuaxn--moreke-juaxn--mori-qsakurais-certifiedxn--mosjen-eyawaraxn--mot-tlazioxn--mre-og-romsdal-qqbuseranishiaritakurashikis-foundationxn--msy-ula0hakubaghdadultravelchannelxn--mtta-vrjjat-k7aflakstadaokagakicks-assnasaarlandxn--muost-0qaxn--mxtq1minisitexn--ngbc5azdxn--ngbe9e0axn--ngbrxn--45q11citadelhicampinashikiminohostfoldnavyxn--nit225koshimizumakiyosunnydayxn--nmesjevuemie-tcbalestrandabergamoarekeymachineustarnbergxn--nnx388axn--nodessakyotanabelaudiopsysynology-dstreamlitappittsburghofficialxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeserveftplanetariuminamitanexn--nvuotna-hwaxn--nyqy26axn--o1achernihivgubsxn--o3cw4hakuis-a-democratravelersinsurancexn--o3cyx2axn--od0algxn--od0aq3belementorayoshiokanumazuryukuhashimojibxos3-website-ap-southeast-1xn--ogbpf8flatangerxn--oppegrd-ixaxn--ostery-fyawatahamaxn--osyro-wuaxn--otu796dxn--p1acfedorapeoplegoismailillehammerfeste-ipatriaxn--p1ais-gonexn--pgbs0dhlx3xn--porsgu-sta26fedoraprojectoyotsukaidoxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4cngreaterxn--qcka1pmcpenzaporizhzhiaxn--qqqt11minnesotaketakayamassivegridxn--qxa6axn--qxamsterdamnserverbaniaxn--rady-iraxn--rdal-poaxn--rde-ulaxn--rdy-0nabaris-into-animeetrentin-sued-tirolxn--rennesy-v1axn--rhkkervju-01afeiraquarelleasingujaratoyouraxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5naturbruksgymnxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byaxn--rny31hakusanagochihayaakasakawaiishopitsitexn--rovu88bellevuelosangeles3-website-ap-southeast-2xn--rros-granvindafjordxn--rskog-uuaxn--rst-0naturhistorischesxn--rsta-framercanvasxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byaxn--s-1faithaldenxn--s9brj9cnpyatigorskolecznagatorodoyxn--sandnessjen-ogbellunord-odalombardyn53xn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphoxn--4dbgdty6citichernovtsyncloudrangedaluccarbonia-iglesias-carboniaiglesiascarboniaxn--skierv-utazasxn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5natuurwetenschappenginexn--slt-elabcieszynh-servebeero-stageiseiroumuenchencoreapigeelvinckoshunantankmpspawnextdirectrentino-s-tirolxn--smla-hraxn--smna-gratangentlentapisa-geekosugexn--snase-nraxn--sndre-land-0cbeneventochiokinoshimaintenancebinordreisa-hockeynutazurestaticappspaceusercontentateyamaveroykenglandeltaitogitsumitakagiizeasypanelblagrarchaeologyeongbuk0emmafann-arboretumbriamallamaceiobbcg123homepagefrontappchizip61123minsidaarborteaches-yogasawaracingroks-theatree123hjemmesidealerimo-i-rana4u2-localhistorybolzano-altoadigeometre-experts-comptables3-ap-northeast-123miwebcambridgehirn4t3l3p0rtarumizusawabogadobeaemcloud-fr123paginaweberkeleyokosukanrabruzzombieidskoguchikushinonsenasakuchinotsuchiurakawafaicloudineat-url-o-g-i-naval-d-aosta-valleyokote164-b-datacentermezproxyzgoraetnabudejjudaicadaquest-mon-blogueurodirumaceratabuseating-organicbcn-north-123saitamakawabartheshopencraftrainingdyniajuedischesapeakebayernavigationavoi234lima-cityeats3-ap-northeast-20001wwwedeployokozeastasiamunemurorangecloudplatform0xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbentleyurihonjournalistjohnikonanporovnobserverxn--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bulls-fanxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbeppublishproxyusuharavocatanzarowegroweiboltashkentatamotorsitestingivingjemnes3-eu-central-1kappleadpages-12hpalmspringsakerxn--stre-toten-zcbeskidyn-ip24xn--t60b56axn--tckweddingxn--tiq49xqyjelasticbeanstalkhmelnitskiyamarumorimachidaxn--tjme-hraxn--tn0agrocerydxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbestbuyshoparenagareyamaizurugbyenvironmentalconservationflashdrivefsnillfjordiscordsezjampaleoceanographics3-website-eu-west-1xn--trentin-sdtirol-7vbetainaboxfuseekloges3-website-sa-east-1xn--trentino-sd-tirol-c3bhzcasertainaioirasebastopologyeongnamegawafflecellclstagemologicaliforniavoues3-eu-west-1xn--trentino-sdtirol-szbielawalbrzycharitypedreamhostersvp4xn--trentinosd-tirol-rzbiellaakesvuemieleccebizenakanotoddeninoheguriitatebayashiibahcavuotnagaivuotnagaokakyotambabybluebitelevisioncilla-speziaxarnetbank8s3-eu-west-2xn--trentinosdtirol-7vbieszczadygeyachimataijiiyamanouchikuhokuryugasakitaurayasudaxn--trentinsd-tirol-6vbievat-band-campaignieznombrendlyngengerdalces3-website-us-east-1xn--trentinsdtirol-nsbifukagawalesundiscountypeformelhusgardeninomiyakonojorpelandiscourses3-website-us-west-1xn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvestre-slidrexn--uc0ay4axn--uist22halsakakinokiaxn--uisz3gxn--unjrga-rtarnobrzegyptianxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtularvikonskowolayangroupiemontexn--valle-d-aoste-ehboehringerikexn--valleaoste-e7axn--valledaoste-ebbvadsoccerxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbigvalledaostaobaomoriguchiharag-cloud-championshiphoplixboxenirasakincheonishiazaindependent-commissionishigouvicasinordeste-idclkarasjohkamikitayamatsurindependent-inquest-a-la-masionishiharaxn--vestvgy-ixa6oxn--vg-yiabkhaziaxn--vgan-qoaxn--vgsy-qoa0jelenia-goraxn--vgu402cnsantabarbaraxn--vhquvestre-totennishiawakuraxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861biharstadotsubetsugaruhrxn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1cntjomeldaluroyxn--wgbl6axn--xhq521bihorologyusuisservegame-serverxn--xkc2al3hye2axn--xkc2dl3a5ee0hammarfeastafricaravantaaxn--y9a3aquariumintereitrentino-sudtirolxn--yer-znaumburgxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4dbrk0cexn--ystre-slidre-ujbikedaejeonbukarasjokarasuyamarriottatsunoceanographiquehimejindependent-inquiryuufcfanishiizunazukindependent-panelomoliseminemrxn--zbx025dxn--zf0ao64axn--zf0avxlxn--zfr164bilbaogashimadachicagoboavistanbulsan-sudtirolbia-tempio-olbiatempioolbialystokkeliwebredirectme-south-1xnbayxz \ No newline at end of file +birkenesoddtangentinglogoweirbitbucketrzynishikatakayamatta-varjjatjomembersaltdalovepopartysfjordiskussionsbereichatinhlfanishikatsuragitappassenger-associationishikawazukamiokameokamakurazakitaurayasudabitternidisrechtrainingloomy-routerbjarkoybjerkreimdbalsan-suedtirololitapunkapsienamsskoganeibmdeveloperauniteroirmemorialombardiadempresashibetsukumiyamagasakinderoyonagunicloudevelopmentaxiijimarriottayninhaccanthobby-siteval-d-aosta-valleyoriikaracolognebinatsukigataiwanumatajimidsundgcahcesuolocustomer-ocimperiautoscanalytics-gatewayonagoyaveroykenflfanpachihayaakasakawaiishopitsitemasekd1kappenginedre-eikerimo-siemenscaledekaascolipicenoboribetsucks3-eu-west-3utilities-16-balestrandabergentappsseekloges3-eu-west-123paginawebcamauction-acornfshostrodawaraktyubinskaunicommbank123kotisivultrobjectselinogradimo-i-rana4u2-localhostrolekanieruchomoscientistordal-o-g-i-nikolaevents3-ap-northeast-2-ddnsking123homepagefrontappchizip61123saitamakawababia-goracleaningheannakadomarineat-urlimanowarudakuneustarostwodzislawdev-myqnapcloudcontrolledgesuite-stagingdyniamusementdllclstagehirnikonantomobelementorayokosukanoyakumoliserniaurland-4-salernord-aurdalipaywhirlimiteddnslivelanddnss3-ap-south-123siteweberlevagangaviikanonji234lima-cityeats3-ap-southeast-123webseiteambulancechireadmyblogspotaribeiraogakicks-assurfakefurniturealmpmninoheguribigawaurskog-holandinggfarsundds3-ap-southeast-20001wwwedeployokote123hjemmesidealerdalaheadjuegoshikibichuobiraustevollimombetsupplyokoze164-balena-devices3-ca-central-123websiteleaf-south-12hparliamentatsunobninsk8s3-eu-central-1337bjugnishimerablackfridaynightjxn--11b4c3ditchyouripatriabloombergretaijindustriesteinkjerbloxcmsaludivtasvuodnakaiwanairlinekobayashimodatecnologiablushakotanishinomiyashironomniwebview-assetsalvadorbmoattachmentsamegawabmsamnangerbmwellbeingzonebnrweatherchannelsdvrdnsamparalleluxenishinoomotegotsukishiwadavvenjargamvikarpaczest-a-la-maisondre-landivttasvuotnakamai-stagingloppennebomlocalzonebonavstackartuzybondigitaloceanspacesamsclubartowest1-usamsunglugsmall-webspacebookonlineboomlaakesvuemielecceboschristmasakilatiron-riopretoeidsvollovesickaruizawabostik-serverrankoshigayachtsandvikcoromantovalle-d-aostakinouebostonakijinsekikogentlentapisa-geekarumaifmemsetkmaxxn--12c1fe0bradescotksatmpaviancapitalonebouncemerckmsdscloudiybounty-fullensakerrypropertiesangovtoyosatoyokawaboutiquebecologialaichaugiangmbhartiengiangminakamichiharaboutireservdrangedalpusercontentoyotapfizerboyfriendoftheinternetflixn--12cfi8ixb8lublindesnesanjosoyrovnoticiasannanishinoshimattelemarkasaokamikitayamatsurinfinitigopocznore-og-uvdalucaniabozen-sudtiroluccanva-appstmnishiokoppegardray-dnsupdaterbozen-suedtirolukowesteuropencraftoyotomiyazakinsurealtypeformesswithdnsannohekinanporovigonohejinternationaluroybplacedogawarabikomaezakirunordkappgfoggiabrandrayddns5ybrasiliadboxoslockerbresciaogashimadachicappadovaapstemp-dnswatchest-mon-blogueurodirumagazinebrindisiciliabroadwaybroke-itvedestrandraydnsanokashibatakashimashikiyosatokigawabrokerbrothermesserlifestylebtimnetzpisdnpharmaciensantamariakebrowsersafetymarketingmodumetacentrumeteorappharmacymruovatlassian-dev-builderschaefflerbrumunddalutskashiharabrusselsantoandreclaimsanukintlon-2bryanskiptveterinaireadthedocsaobernardovre-eikerbrynebwestus2bzhitomirbzzwhitesnowflakecommunity-prochowicecomodalenissandoycompanyaarphdfcbankasumigaurawa-mazowszexn--1ck2e1bambinagisobetsuldalpha-myqnapcloudaccess3-us-east-2ixboxeroxfinityolasiteastus2comparemarkerryhotelsaves-the-whalessandria-trani-barletta-andriatranibarlettaandriacomsecaasnesoddeno-stagingrondarcondoshifteditorxn--1ctwolominamatarnobrzegrongrossetouchijiwadedyn-berlincolnissayokoshibahikariyaltakazakinzais-a-bookkeepermarshallstatebankasuyalibabahccavuotnagaraholtaleniwaizumiotsurugashimaintenanceomutazasavonarviikaminoyamaxunispaceconferenceconstructionflashdrivefsncf-ipfsaxoconsuladobeio-static-accesscamdvrcampaniaconsultantranoyconsultingroundhandlingroznysaitohnoshookuwanakayamangyshlakdnepropetrovskanlandyndns-freeboxostrowwlkpmgrphilipsyno-dschokokekscholarshipschoolbusinessebycontactivetrailcontagematsubaravendbambleborkdalvdalcest-le-patron-rancherkasydneyukuhashimokawavoues3-sa-east-1contractorskenissedalcookingruecoolblogdnsfor-better-thanhhoarairforcentralus-1cooperativano-frankivskodjeephonefosschoolsztynsetransiphotographysiocoproductionschulplattforminamiechizenisshingucciprianiigatairaumalatvuopmicrolightinguidefinimaringatlancastercorsicafjschulservercosenzakopanecosidnshome-webservercellikescandypopensocialcouchpotatofrieschwarzgwangjuh-ohtawaramotoineppueblockbusternopilawacouncilcouponscrapper-sitecozoravennaharimalborkaszubytemarketscrappinguitarscrysecretrosnubananarepublic-inquiryurihonjoyenthickaragandaxarnetbankanzakiwielunnerepairbusanagochigasakishimabarakawaharaolbia-tempio-olbiatempioolbialowiezachpomorskiengiangjesdalolipopmcdirepbodyn53cqcxn--1lqs03niyodogawacrankyotobetsumidaknongujaratmallcrdyndns-homednscwhminamifuranocreditcardyndns-iphutholdingservehttpbincheonl-ams-1creditunionionjukujitawaravpagecremonashorokanaiecrewhoswholidaycricketnedalcrimeast-kazakhstanangercrotonecrowniphuyencrsvp4cruiseservehumourcuisinellair-traffic-controllagdenesnaaseinet-freakserveircasertainaircraftingvolloansnasaarlanduponthewifidelitypedreamhostersaotomeldaluxurycuneocupcakecuritibacgiangiangryggeecurvalled-aostargets-itranslatedyndns-mailcutegirlfriendyndns-office-on-the-webhoptogurafedoraprojectransurlfeirafembetsukuis-a-bruinsfanfermodenakasatsunairportrapaniizaferraraferraris-a-bulls-fanferrerotikagoshimalopolskanittedalfetsundyndns-wikimobetsumitakagildeskaliszkolamericanfamilydservemp3fgunmaniwamannorth-kazakhstanfhvalerfilegear-augustowiiheyakagefilegear-deatnuniversitysvardofilegear-gbizfilegear-iefilegear-jpmorgangwonporterfilegear-sg-1filminamiizukamiminefinalchikugokasellfyis-a-candidatefinancefinnoyfirebaseappiemontefirenetlifylkesbiblackbaudcdn-edgestackhero-networkinggroupowiathletajimabaria-vungtaudiopsysharpigboatshawilliamhillfirenzefirestonefireweblikes-piedmontravelersinsurancefirmdalegalleryfishingoldpoint2thisamitsukefitjarfitnessettsurugiminamimakis-a-catererfjalerfkatsushikabeebyteappilottonsberguovdageaidnunjargausdalflekkefjordyndns-workservep2phxn--1lqs71dyndns-remotewdyndns-picserveminecraftransporteflesbergushikamifuranorthflankatsuyamashikokuchuoflickragerokunohealthcareershellflierneflirfloginlinefloppythonanywherealtorfloraflorencefloripalmasfjordenfloristanohatajiris-a-celticsfanfloromskogxn--2m4a15eflowershimokitayamafltravinhlonganflynnhosting-clusterfncashgabadaddjabbottoyourafndyndns1fnwkzfolldalfoolfor-ourfor-somegurownproviderfor-theaterfordebianforexrotheworkpccwinbar0emmafann-arborlandd-dnsiskinkyowariasahikawarszawashtenawsmppl-wawsglobalacceleratorahimeshimakanegasakievennodebalancern4t3l3p0rtatarantours3-ap-northeast-123minsidaarborteaches-yogano-ipifony-123miwebaccelastx4432-b-datacenterprisesakijobservableusercontentateshinanomachintaifun-dnsdojournalistoloseyouriparisor-fronavuotnarashinoharaetnabudejjunipereggio-emilia-romagnaroyboltateyamajureggiocalabriakrehamnayoro0o0forgotdnshimonitayanagithubpreviewsaikisarazure-mobileirfjordynnservepicservequakeforli-cesena-forlicesenaforlillehammerfeste-ipimientaketomisatoolshimonosekikawaforsalegoismailillesandefjordynservebbservesarcasmileforsandasuolodingenfortalfortefosneshimosuwalkis-a-chefashionstorebaseljordyndns-serverisignfotrdynulvikatowicefoxn--2scrj9casinordlandurbanamexnetgamersapporomurafozfr-1fr-par-1fr-par-2franamizuhoboleslawiecommerce-shoppingyeongnamdinhachijohanamakisofukushimaoris-a-conservativegarsheiheijis-a-cparachutingfredrikstadynv6freedesktopazimuthaibinhphuocelotenkawakayamagnetcieszynh-servebeero-stageiseiroumugifuchungbukharag-cloud-championshiphoplixn--30rr7yfreemyiphosteurovisionredumbrellangevagrigentobishimadridvagsoygardenebakkeshibechambagricoharugbydgoszczecin-berlindasdaburfreesitefreetlshimotsukefreisennankokubunjis-a-cubicle-slavellinodeobjectshimotsumafrenchkisshikindleikangerfreseniushinichinanfriuli-v-giuliafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganshinjotelulubin-vpncateringebunkyonanaoshimamateramockashiwarafrognfrolandynvpnpluservicesevastopolitiendafrom-akamaized-stagingfrom-alfrom-arfrom-azurewebsiteshikagamiishibuyabukihokuizumobaragusabaerobaticketshinjukuleuvenicefrom-campobassociatest-iserveblogsytenrissadistdlibestadultrentin-sudtirolfrom-coachaseljeducationcillahppiacenzaganfrom-ctrentin-sued-tirolfrom-dcatfooddagestangefrom-decagliarikuzentakataikillfrom-flapymntrentin-suedtirolfrom-gap-east-1from-higashiagatsumagoianiafrom-iafrom-idyroyrvikingulenfrom-ilfrom-in-the-bandairtelebitbridgestonemurorangecloudplatform0from-kshinkamigototalfrom-kyfrom-langsonyantakahamalselveruminamiminowafrom-malvikaufentigerfrom-mdfrom-mein-vigorlicefrom-mifunefrom-mnfrom-modshinshinotsurgeryfrom-mshinshirofrom-mtnfrom-ncatholicurus-4from-ndfrom-nefrom-nhs-heilbronnoysundfrom-njshintokushimafrom-nminamioguni5from-nvalledaostargithubusercontentrentino-a-adigefrom-nycaxiaskvollpagesardegnarutolgaulardalvivanovoldafrom-ohdancefrom-okegawassamukawataris-a-democratrentino-aadigefrom-orfrom-panasonichernovtsykkylvenneslaskerrylogisticsardiniafrom-pratohmamurogawatsonrenderfrom-ris-a-designerimarugame-hostyhostingfrom-schmidtre-gauldalfrom-sdfrom-tnfrom-txn--32vp30hachinoheavyfrom-utsiracusagaeroclubmedecin-addrammenuorodoyerfrom-val-daostavalleyfrom-vtrentino-alto-adigefrom-wafrom-wiardwebthingsjcbnpparibashkiriafrom-wvallee-aosteroyfrom-wyfrosinonefrostabackplaneapplebesbyengerdalp1froyal-commissionfruskydivingfujiiderafujikawaguchikonefujiminokamoenairtrafficplexus-2fujinomiyadapliefujiokazakinkobearalvahkikonaibetsubame-south-1fujisatoshoeshintomikasaharafujisawafujishiroishidakabiratoridediboxn--3bst00minamisanrikubetsupportrentino-altoadigefujitsuruokakamigaharafujiyoshidappnodearthainguyenfukayabeardubaikawagoefukuchiyamadatsunanjoburgfukudomigawafukuis-a-doctorfukumitsubishigakirkeneshinyoshitomiokamisatokamachippubetsuikitchenfukuokakegawafukuroishikariwakunigamigrationfukusakirovogradoyfukuyamagatakaharunusualpersonfunabashiriuchinadattorelayfunagatakahashimamakiryuohkurafunahashikamiamakusatsumasendaisenergyeongginowaniihamatamakinoharafundfunkfeuerfuoiskujukuriyamandalfuosskoczowindowskrakowinefurubirafurudonordreisa-hockeynutwentertainmentrentino-s-tirolfurukawajimangolffanshiojirishirifujiedafusoctrangfussagamiharafutabayamaguchinomihachimanagementrentino-stirolfutboldlygoingnowhere-for-more-og-romsdalfuttsurutashinais-a-financialadvisor-aurdalfuturecmshioyamelhushirahamatonbetsurnadalfuturehostingfuturemailingfvghakuis-a-gurunzenhakusandnessjoenhaldenhalfmoonscalebookinghostedpictetrentino-sud-tirolhalsakakinokiaham-radio-opinbar1hamburghammarfeastasiahamurakamigoris-a-hard-workershiraokamisunagawahanamigawahanawahandavvesiidanangodaddyn-o-saurealestatefarmerseinehandcrafteducatorprojectrentino-sudtirolhangglidinghangoutrentino-sued-tirolhannannestadhannosegawahanoipinkazohanyuzenhappouzshiratakahagianghasamap-northeast-3hasaminami-alpshishikuis-a-hunterhashbanghasudazaifudaigodogadobeioruntimedio-campidano-mediocampidanomediohasura-appinokokamikoaniikappudopaashisogndalhasvikazteleportrentino-suedtirolhatogayahoooshikamagayaitakamoriokakudamatsuehatoyamazakitahiroshimarcheapartmentshisuifuettertdasnetzhatsukaichikaiseiyoichipshitaramahattfjelldalhayashimamotobusells-for-lesshizukuishimoichilloutsystemscloudsitehazuminobushibukawahelplfinancialhelsinkitakamiizumisanofidonnakamurataitogliattinnhemneshizuokamitondabayashiogamagoriziahemsedalhepforgeblockshoujis-a-knightpointtokaizukamaishikshacknetrentinoa-adigehetemlbfanhigashichichibuzentsujiiehigashihiroshimanehigashiizumozakitakatakanabeautychyattorneyagawakkanaioirasebastopoleangaviikadenagahamaroyhigashikagawahigashikagurasoedahigashikawakitaaikitakyushunantankazunovecorebungoonow-dnshowahigashikurumeinforumzhigashimatsushimarnardalhigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshowtimeloyhigashinarusells-for-uhigashinehigashiomitamanoshiroomghigashiosakasayamanakakogawahigashishirakawamatakanezawahigashisumiyoshikawaminamiaikitamihamadahigashitsunospamproxyhigashiurausukitamotosunnydayhigashiyamatokoriyamanashiibaclieu-1higashiyodogawahigashiyoshinogaris-a-landscaperspectakasakitanakagusukumoldeliveryhippyhiraizumisatohokkaidontexistmein-iservschulecznakaniikawatanagurahirakatashinagawahiranais-a-lawyerhirarahiratsukaeruhirayaizuwakamatsubushikusakadogawahitachiomiyaginozawaonsensiositehitachiotaketakaokalmykiahitraeumtgeradegreehjartdalhjelmelandholyhomegoodshwinnersiiitesilkddiamondsimple-urlhomeipioneerhomelinkyard-cloudjiffyresdalhomelinuxn--3ds443ghomeofficehomesecuritymacaparecidahomesecuritypchiryukyuragiizehomesenseeringhomeskleppippugliahomeunixn--3e0b707ehondahonjyoitakarazukaluganskfh-muensterhornindalhorsells-itrentinoaadigehortendofinternet-dnsimplesitehospitalhotelwithflightsirdalhotmailhoyangerhoylandetakasagooglecodespotrentinoalto-adigehungyenhurdalhurumajis-a-liberalhyllestadhyogoris-a-libertarianhyugawarahyundaiwafuneis-very-evillasalleitungsenis-very-goodyearis-very-niceis-very-sweetpepperugiais-with-thebandoomdnstraceisk01isk02jenv-arubacninhbinhdinhktistoryjeonnamegawajetztrentinostiroljevnakerjewelryjgorajlljls-sto1jls-sto2jls-sto3jmpixolinodeusercontentrentinosud-tiroljnjcloud-ver-jpchitosetogitsuliguriajoyokaichibahcavuotnagaivuotnagaokakyotambabymilk3jozis-a-musicianjpnjprsolarvikhersonlanxessolundbeckhmelnitskiyamasoykosaigawakosakaerodromegalloabatobamaceratachikawafaicloudineencoreapigeekoseis-a-painterhostsolutionslupskhakassiakosheroykoshimizumakis-a-patsfankoshughesomakosugekotohiradomainstitutekotourakouhokumakogenkounosupersalevangerkouyamasudakouzushimatrixn--3pxu8khplaystation-cloudyclusterkozagawakozakis-a-personaltrainerkozowiosomnarviklabudhabikinokawachinaganoharamcocottekpnkppspbarcelonagawakepnord-odalwaysdatabaseballangenkainanaejrietisalatinabenogiehtavuoatnaamesjevuemielnombrendlyngen-rootaruibxos3-us-gov-west-1krasnikahokutokonamegatakatoris-a-photographerokussldkrasnodarkredstonekrelliankristiansandcatsoowitdkmpspawnextdirectrentinosudtirolkristiansundkrodsheradkrokstadelvaldaostavangerkropyvnytskyis-a-playershiftcryptonomichinomiyakekryminamiyamashirokawanabelaudnedalnkumamotoyamatsumaebashimofusakatakatsukis-a-republicanonoichinosekigaharakumanowtvaokumatorinokumejimatsumotofukekumenanyokkaichirurgiens-dentistes-en-francekundenkunisakis-a-rockstarachowicekunitachiaraisaijolsterkunitomigusukukis-a-socialistgstagekunneppubtlsopotrentinosued-tirolkuokgroupizzakurgankurobegetmyipirangalluplidlugolekagaminorddalkurogimimozaokinawashirosatochiokinoshimagentositempurlkuroisodegaurakuromatsunais-a-soxfankuronkurotakikawasakis-a-studentalkushirogawakustanais-a-teacherkassyncloudkusuppliesor-odalkutchanelkutnokuzumakis-a-techietipslzkvafjordkvalsundkvamsterdamnserverbaniakvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspdnsor-varangermishimatsusakahogirlymisugitokorozawamitakeharamitourismartlabelingmitoyoakemiuramiyazurecontainerdpoliticaobangmiyotamatsukuris-an-actormjondalenmonzabrianzaramonzaebrianzamonzaedellabrianzamordoviamorenapolicemoriyamatsuuramoriyoshiminamiashigaramormonstermoroyamatsuzakis-an-actressmushcdn77-sslingmortgagemoscowithgoogleapiszmoseushimogosenmosjoenmoskenesorreisahayakawakamiichikawamisatottoris-an-anarchistjordalshalsenmossortlandmosviknx-serversusakiyosupabaseminemotegit-reposoruminanomoviemovimientokyotangotembaixadattowebhareidsbergmozilla-iotrentinosuedtirolmtranbytomaridagawalmartrentinsud-tirolmuikaminokawanishiaizubangemukoelnmunakatanemuosattemupkomatsushimassa-carrara-massacarraramassabuzzmurmanskomforbar2murotorcraftranakatombetsumy-gatewaymusashinodesakegawamuseumincomcastoripressorfoldmusicapetownnews-stagingmutsuzawamy-vigormy-wanggoupilemyactivedirectorymyamazeplaymyasustor-elvdalmycdmycloudnsoundcastorjdevcloudfunctionsokndalmydattolocalcertificationmyddnsgeekgalaxymydissentrentinsudtirolmydobissmarterthanyoumydrobofageometre-experts-comptablesowamydspectruminisitemyeffectrentinsued-tirolmyfastly-edgekey-stagingmyfirewalledreplittlestargardmyforuminterecifedextraspace-to-rentalstomakomaibaramyfritzmyftpaccesspeedpartnermyhome-servermyjinomykolaivencloud66mymailermymediapchoseikarugalsacemyokohamamatsudamypeplatformsharis-an-artistockholmestrandmypetsphinxn--41amyphotoshibajddarvodkafjordvaporcloudmypictureshinomypsxn--42c2d9amysecuritycamerakermyshopblockspjelkavikommunalforbundmyshopifymyspreadshopselectrentinsuedtirolmytabitordermythic-beastspydebergmytis-a-anarchistg-buildermytuleap-partnersquaresindevicenzamyvnchoshichikashukudoyamakeuppermywirecipescaracallypoivronpokerpokrovskommunepolkowicepoltavalle-aostavernpomorzeszowithyoutuberspacekitagawaponpesaro-urbino-pesarourbinopesaromasvuotnaritakurashikis-bykleclerchitachinakagawaltervistaipeigersundynamic-dnsarlpordenonepornporsangerporsangugeporsgrunnanpoznanpraxihuanprdprgmrprimetelprincipeprivatelinkomonowruzhgorodeoprivatizehealthinsuranceprofesionalprogressivegasrlpromonza-e-della-brianzaptokuyamatsushigepropertysnesrvarggatrevisogneprotectionprotonetroandindependent-inquest-a-la-masionprudentialpruszkowiwatsukiyonotaireserve-onlineprvcyonabarumbriaprzeworskogpunyufuelpupulawypussycatanzarowixsitepvhachirogatakahatakaishimojis-a-geekautokeinotteroypvtrogstadpwchowderpzqhadanorthwesternmutualqldqotoyohashimotoshimaqponiatowadaqslgbtroitskomorotsukagawaqualifioapplatter-applatterplcube-serverquangngais-certifiedugit-pagespeedmobilizeroticaltanissettailscaleforcequangninhthuanquangtritonoshonais-foundationquickconnectromsakuragawaquicksytestreamlitapplumbingouvaresearchitectesrhtrentoyonakagyokutoyakomakizunokunimimatakasugais-an-engineeringquipelementstrippertuscanytushungrytuvalle-daostamayukis-into-animeiwamizawatuxfamilytuyenquangbinhthuantwmailvestnesuzukis-gonevestre-slidreggio-calabriavestre-totennishiawakuravestvagoyvevelstadvibo-valentiaavibovalentiavideovinhphuchromedicinagatorogerssarufutsunomiyawakasaikaitakokonoevinnicarbonia-iglesias-carboniaiglesiascarboniavinnytsiavipsinaapplurinacionalvirginanmokurennebuvirtual-userveexchangevirtualservervirtualuserveftpodhalevisakurais-into-carsnoasakuholeckodairaviterboliviajessheimmobilienvivianvivoryvixn--45br5cylvlaanderennesoyvladikavkazimierz-dolnyvladimirvlogintoyonezawavmintsorocabalashovhachiojiyahikobierzycevologdanskoninjambylvolvolkswagencyouvolyngdalvoorlopervossevangenvotevotingvotoyonovps-hostrowiechungnamdalseidfjordynathomebuiltwithdarkhangelskypecorittogojomeetoystre-slidrettozawawmemergencyahabackdropalermochizukikirarahkkeravjuwmflabsvalbardunloppadualstackomvuxn--3hcrj9chonanbuskerudynamisches-dnsarpsborgripeeweeklylotterywoodsidellogliastradingworse-thanhphohochiminhadselbuyshouseshirakolobrzegersundongthapmircloudletshiranukamishihorowowloclawekonskowolawawpdevcloudwpenginepoweredwphostedmailwpmucdnipropetrovskygearappodlasiellaknoluoktagajobojis-an-entertainerwpmudevcdnaccessojamparaglidingwritesthisblogoipodzonewroclawmcloudwsseoullensvanguardianwtcp4wtfastlylbanzaicloudappspotagereporthruherecreationinomiyakonojorpelandigickarasjohkameyamatotakadawuozuerichardlillywzmiuwajimaxn--4it797konsulatrobeepsondriobranconagareyamaizuruhrxn--4pvxs4allxn--54b7fta0ccistrondheimpertrixcdn77-secureadymadealstahaugesunderxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49citadelhichisochimkentozsdell-ogliastraderxn--5rtq34kontuminamiuonumatsunoxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264citicarrdrobakamaiorigin-stagingmxn--12co0c3b4evalleaostaobaomoriguchiharaffleentrycloudflare-ipfstcgroupaaskimitsubatamibulsan-suedtirolkuszczytnoopscbgrimstadrrxn--80aaa0cvacationsvchoyodobashichinohealth-carereforminamidaitomanaustdalxn--80adxhksveioxn--80ao21axn--80aqecdr1axn--80asehdbarclaycards3-us-west-1xn--80aswgxn--80aukraanghkeliwebpaaskoyabeagleboardxn--8dbq2axn--8ltr62konyvelohmusashimurayamassivegridxn--8pvr4uxn--8y0a063axn--90a1affinitylotterybnikeisencowayxn--90a3academiamicable-modemoneyxn--90aeroportsinfolionetworkangerxn--90aishobaraxn--90amckinseyxn--90azhytomyrxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byanagawaxn--asky-iraxn--aurskog-hland-jnbarclays3-us-west-2xn--avery-yuasakurastoragexn--b-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbsvelvikongsbergxn--bck1b9a5dre4civilaviationfabricafederation-webredirectmediatechnologyeongbukashiwazakiyosembokutamamuraxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--bjarky-fyanaizuxn--bjddar-ptarumizusawaxn--blt-elabcienciamallamaceiobbcn-north-1xn--bmlo-graingerxn--bod-2natalxn--bozen-sdtirol-2obanazawaxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-aptibleadpagesquare7xn--brum-voagatrustkanazawaxn--btsfjord-9zaxn--bulsan-sdtirol-nsbarefootballooningjovikarasjoketokashikiyokawaraxn--c1avgxn--c2br7gxn--c3s14misakis-a-therapistoiaxn--cck2b3baremetalombardyn-vpndns3-website-ap-northeast-1xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-into-cartoonsokamitsuexn--ciqpnxn--clchc0ea0b2g2a9gcdxn--czr694bargainstantcloudfrontdoorestauranthuathienhuebinordre-landiherokuapparochernigovernmentjeldsundiscordsays3-website-ap-southeast-1xn--czrs0trvaroyxn--czru2dxn--czrw28barrel-of-knowledgeapplinziitatebayashijonawatebizenakanojoetsumomodellinglassnillfjordiscordsezgoraxn--d1acj3barrell-of-knowledgecomputermezproxyzgorzeleccoffeedbackanagawarmiastalowa-wolayangroupars3-website-ap-southeast-2xn--d1alfaststacksevenassigdalxn--d1atrysiljanxn--d5qv7z876clanbibaiduckdnsaseboknowsitallxn--davvenjrga-y4axn--djrs72d6uyxn--djty4koobindalxn--dnna-grajewolterskluwerxn--drbak-wuaxn--dyry-iraxn--e1a4cldmail-boxaxn--eckvdtc9dxn--efvn9svn-repostuff-4-salexn--efvy88haebaruericssongdalenviknaklodzkochikushinonsenasakuchinotsuchiurakawaxn--ehqz56nxn--elqq16hagakhanhhoabinhduongxn--eveni-0qa01gaxn--f6qx53axn--fct429kooris-a-nascarfanxn--fhbeiarnxn--finny-yuaxn--fiq228c5hsbcleverappsassarinuyamashinazawaxn--fiq64barsycenterprisecloudcontrolappgafanquangnamasteigenoamishirasatochigifts3-website-eu-west-1xn--fiqs8swidnicaravanylvenetogakushimotoganexn--fiqz9swidnikitagatakkomaganexn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbsswiebodzindependent-commissionxn--forlcesena-c8axn--fpcrj9c3dxn--frde-granexn--frna-woaxn--frya-hraxn--fzc2c9e2clickrisinglesjaguarxn--fzys8d69uvgmailxn--g2xx48clinicasacampinagrandebungotakadaemongolianishitosashimizunaminamiawajikintuitoyotsukaidownloadrudtvsaogoncapooguyxn--gckr3f0fastvps-serveronakanotoddenxn--gecrj9cliniquedaklakasamatsudoesntexisteingeekasserversicherungroks-theatrentin-sud-tirolxn--ggaviika-8ya47hagebostadxn--gildeskl-g0axn--givuotna-8yandexcloudxn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-into-gamessinamsosnowieconomiasadojin-dslattuminamitanexn--gmqw5axn--gnstigbestellen-zvbrplsbxn--45brj9churcharterxn--gnstigliefern-wobihirosakikamijimayfirstorfjordxn--h-2failxn--h1ahnxn--h1alizxn--h2breg3eveneswinoujsciencexn--h2brj9c8clothingdustdatadetectrani-andria-barletta-trani-andriaxn--h3cuzk1dienbienxn--hbmer-xqaxn--hcesuolo-7ya35barsyonlinehimejiiyamanouchikujoinvilleirvikarasuyamashikemrevistathellequipmentjmaxxxjavald-aostatics3-website-sa-east-1xn--hebda8basicserversejny-2xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-k3swisstufftoread-booksnestudioxn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyaotsusonoxn--io0a7is-leetrentinoaltoadigexn--j1adpohlxn--j1aefauskedsmokorsetagayaseralingenovaraxn--j1ael8basilicataniaxn--j1amhaibarakisosakitahatakamatsukawaxn--j6w193gxn--jlq480n2rgxn--jlster-byasakaiminatoyookananiimiharuxn--jrpeland-54axn--jvr189misasaguris-an-accountantsmolaquilaocais-a-linux-useranishiaritabashikaoizumizakitashiobaraxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--45q11circlerkstagentsasayamaxn--koluokta-7ya57haiduongxn--kprw13dxn--kpry57dxn--kput3is-lostre-toteneis-a-llamarumorimachidaxn--krager-gyasugitlabbvieeexn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdfastly-terrariuminamiiseharaxn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyasuokanmakiwakuratexn--kvnangen-k0axn--l-1fairwindsynology-diskstationxn--l1accentureklamborghinikkofuefukihabororosynology-dsuzakadnsaliastudynaliastrynxn--laheadju-7yatominamibosoftwarendalenugxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52basketballfinanzjaworznoticeableksvikaratsuginamikatagamilanotogawaxn--lesund-huaxn--lgbbat1ad8jejuxn--lgrd-poacctulaspeziaxn--lhppi-xqaxn--linds-pramericanexpresservegame-serverxn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liacn-northwest-1xn--lten-granvindafjordxn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddesxn--mgb9awbfbsbxn--1qqw23axn--mgba3a3ejtunesuzukamogawaxn--mgba3a4f16axn--mgba3a4fra1-deloittexn--mgba7c0bbn0axn--mgbaakc7dvfsxn--mgbaam7a8haiphongonnakatsugawaxn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00batsfjordiscountry-snowplowiczeladzlgleezeu-2xn--mgbai9azgqp6jelasticbeanstalkharkovalleeaostexn--mgbayh7gparasitexn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mgbpl2fhskopervikhmelnytskyivalleedaostexn--mgbqly7c0a67fbcngroks-thisayamanobeatsaudaxn--mgbqly7cvafricargoboavistanbulsan-sudtirolxn--mgbt3dhdxn--mgbtf8flatangerxn--mgbtx2bauhauspostman-echofunatoriginstances3-website-us-east-1xn--mgbx4cd0abkhaziaxn--mix082fbx-osewienxn--mix891fbxosexyxn--mjndalen-64axn--mk0axindependent-inquiryxn--mk1bu44cnpyatigorskjervoyagexn--mkru45is-not-certifiedxn--mlatvuopmi-s4axn--mli-tlavagiskexn--mlselv-iuaxn--moreke-juaxn--mori-qsakuratanxn--mosjen-eyatsukannamihokksundxn--mot-tlavangenxn--mre-og-romsdal-qqbuservecounterstrikexn--msy-ula0hair-surveillancexn--mtta-vrjjat-k7aflakstadaokayamazonaws-cloud9guacuiababybluebiteckidsmynasushiobaracingrok-freeddnsfreebox-osascoli-picenogatabuseating-organicbcgjerdrumcprequalifymelbourneasypanelblagrarq-authgear-stagingjerstadeltaishinomakilovecollegefantasyleaguenoharauthgearappspacehosted-by-previderehabmereitattoolforgerockyombolzano-altoadigeorgeorgiauthordalandroideporteatonamidorivnebetsukubankanumazuryomitanocparmautocodebergamoarekembuchikumagayagawafflecelloisirs3-external-180reggioemiliaromagnarusawaustrheimbalsan-sudtirolivingitpagexlivornobserveregruhostingivestbyglandroverhalladeskjakamaiedge-stagingivingjemnes3-eu-west-2038xn--muost-0qaxn--mxtq1misawaxn--ngbc5azdxn--ngbe9e0axn--ngbrxn--4dbgdty6ciscofreakamaihd-stagingriwataraindroppdalxn--nit225koryokamikawanehonbetsuwanouchikuhokuryugasakis-a-nursellsyourhomeftpiwatexn--nmesjevuemie-tcbalatinord-frontierxn--nnx388axn--nodessakurawebsozais-savedxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeservehalflifeinsurancexn--nvuotna-hwaxn--nyqy26axn--o1achernivtsicilynxn--4dbrk0cexn--o3cw4hakatanortonkotsunndalxn--o3cyx2axn--od0algardxn--od0aq3beneventodayusuharaxn--ogbpf8fldrvelvetromsohuissier-justicexn--oppegrd-ixaxn--ostery-fyatsushiroxn--osyro-wuaxn--otu796dxn--p1acfedjeezxn--p1ais-slickharkivallee-d-aostexn--pgbs0dhlx3xn--porsgu-sta26fedorainfraclouderaxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4cnsauheradyndns-at-homedepotenzamamicrosoftbankasukabedzin-brbalsfjordietgoryoshiokanravocats3-fips-us-gov-west-1xn--qcka1pmcpenzapposxn--qqqt11misconfusedxn--qxa6axn--qxamunexus-3xn--rady-iraxn--rdal-poaxn--rde-ulazioxn--rdy-0nabaris-uberleetrentinos-tirolxn--rennesy-v1axn--rhkkervju-01afedorapeoplefrakkestadyndns-webhostingujogaszxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5naturalxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byawaraxn--rny31hakodatexn--rovu88bentleyusuitatamotorsitestinglitchernihivgubs3-website-us-west-1xn--rros-graphicsxn--rskog-uuaxn--rst-0naturbruksgymnxn--rsta-framercanvasxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byawatahamaxn--s-1faitheshopwarezzoxn--s9brj9cntraniandriabarlettatraniandriaxn--sandnessjen-ogbentrendhostingliwiceu-3xn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphoxn--4gbriminiserverxn--skierv-utazurestaticappspaceusercontentunkongsvingerxn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5navigationxn--slt-elabogadobeaemcloud-fr1xn--smla-hraxn--smna-gratangenxn--snase-nraxn--sndre-land-0cbeppublishproxyuufcfanirasakindependent-panelomonza-brianzaporizhzhedmarkarelianceu-4xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbeskidyn-ip24xn--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bloggerxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbestbuyshoparenagasakikuchikuseihicampinashikiminohostfoldnavyuzawaxn--stre-toten-zcbetainaboxfuselfipartindependent-reviewegroweibolognagasukeu-north-1xn--t60b56axn--tckweddingxn--tiq49xqyjelenia-goraxn--tjme-hraxn--tn0agrocerydxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbhzc66xn--trentin-sdtirol-7vbialystokkeymachineu-south-1xn--trentino-sd-tirol-c3bielawakuyachimataharanzanishiazaindielddanuorrindigenamerikawauevje-og-hornnes3-website-us-west-2xn--trentino-sdtirol-szbiella-speziaxn--trentinosd-tirol-rzbieszczadygeyachiyodaeguamfamscompute-1xn--trentinosdtirol-7vbievat-band-campaignieznoorstaplesakyotanabellunordeste-idclkarlsoyxn--trentinsd-tirol-6vbifukagawalbrzycharitydalomzaporizhzhiaxn--trentinsdtirol-nsbigv-infolkebiblegnicalvinklein-butterhcloudiscoursesalangenishigotpantheonsitexn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atventuresinstagingxn--uc0ay4axn--uist22hakonexn--uisz3gxn--unjrga-rtashkenturindalxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbturystykaneyamazoexn--valle-d-aoste-ehboehringerikexn--valleaoste-e7axn--valledaoste-ebbvadsoccertmgreaterxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbiharstadotsubetsugarulezajskiervaksdalondonetskarmoyxn--vestvgy-ixa6oxn--vg-yiabruzzombieidskogasawarackmazerbaijan-mayenbaidarmeniaxn--vgan-qoaxn--vgsy-qoa0jellybeanxn--vgu402coguchikuzenishiwakinvestmentsaveincloudyndns-at-workisboringsakershusrcfdyndns-blogsitexn--vhquvestfoldxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861bihoronobeokagakikugawalesundiscoverdalondrinaplesknsalon-1xn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1communexn--wgbl6axn--xhq521bikedaejeonbuk0xn--xkc2al3hye2axn--xkc2dl3a5ee0hakubackyardshiraois-a-greenxn--y9a3aquarelleasingxn--yer-znavois-very-badxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4it168dxn--ystre-slidre-ujbiofficialorenskoglobodoes-itcouldbeworldishangrilamdongnairkitapps-audibleasecuritytacticsxn--0trq7p7nnishiharaxn--zbx025dxn--zf0ao64axn--zf0avxlxn--zfr164bipartsaloonishiizunazukindustriaxnbayernxz \ No newline at end of file diff --git a/vendor/golang.org/x/net/publicsuffix/table.go b/vendor/golang.org/x/net/publicsuffix/table.go index 6bdadcc448..78d400fa65 100644 --- a/vendor/golang.org/x/net/publicsuffix/table.go +++ b/vendor/golang.org/x/net/publicsuffix/table.go @@ -4,7 +4,7 @@ package publicsuffix import _ "embed" -const version = "publicsuffix.org's public_suffix_list.dat, git revision e248cbc92a527a166454afe9914c4c1b4253893f (2022-11-15T18:02:38Z)" +const version = "publicsuffix.org's public_suffix_list.dat, git revision 63cbc63d470d7b52c35266aa96c4c98c96ec499c (2023-08-03T10:01:25Z)" const ( nodesBits = 40 @@ -26,7 +26,7 @@ const ( ) // numTLD is the number of top level domains. -const numTLD = 1494 +const numTLD = 1474 // text is the combined text of all labels. // @@ -63,8 +63,8 @@ var nodes uint40String //go:embed data/children var children uint32String -// max children 718 (capacity 1023) -// max text offset 32976 (capacity 65535) -// max text length 36 (capacity 63) -// max hi 9656 (capacity 16383) -// max lo 9651 (capacity 16383) +// max children 743 (capacity 1023) +// max text offset 30876 (capacity 65535) +// max text length 31 (capacity 63) +// max hi 9322 (capacity 16383) +// max lo 9317 (capacity 16383) diff --git a/vendor/golang.org/x/text/unicode/norm/trie.go b/vendor/golang.org/x/text/unicode/norm/trie.go index 423386bf43..e4250ae22c 100644 --- a/vendor/golang.org/x/text/unicode/norm/trie.go +++ b/vendor/golang.org/x/text/unicode/norm/trie.go @@ -29,7 +29,7 @@ var ( nfkcData = newNfkcTrie(0) ) -// lookupValue determines the type of block n and looks up the value for b. +// lookup determines the type of block n and looks up the value for b. // For n < t.cutoff, the block is a simple lookup table. Otherwise, the block // is a list of ranges with an accompanying value. Given a matching range r, // the value for b is by r.value + (b - r.lo) * stride. diff --git a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go index 18a002f82a..0454cdd78e 100644 --- a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go +++ b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go @@ -8,7 +8,6 @@ package packagesdriver import ( "context" "fmt" - "go/types" "strings" "golang.org/x/tools/internal/gocommand" @@ -16,7 +15,7 @@ import ( var debug = false -func GetSizesGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner *gocommand.Runner) (types.Sizes, error) { +func GetSizesForArgsGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner *gocommand.Runner) (string, string, error) { inv.Verb = "list" inv.Args = []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"} stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) @@ -29,21 +28,21 @@ func GetSizesGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner * inv.Args = []string{"GOARCH"} envout, enverr := gocmdRunner.Run(ctx, inv) if enverr != nil { - return nil, enverr + return "", "", enverr } goarch = strings.TrimSpace(envout.String()) compiler = "gc" } else { - return nil, friendlyErr + return "", "", friendlyErr } } else { fields := strings.Fields(stdout.String()) if len(fields) < 2 { - return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \" \":\nstdout: <<%s>>\nstderr: <<%s>>", + return "", "", fmt.Errorf("could not parse GOARCH and Go compiler in format \" \":\nstdout: <<%s>>\nstderr: <<%s>>", stdout.String(), stderr.String()) } goarch = fields[0] compiler = fields[1] } - return types.SizesFor(compiler, goarch), nil + return compiler, goarch, nil } diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 58230038a7..b5de9cf9f2 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -9,7 +9,6 @@ import ( "context" "encoding/json" "fmt" - "go/types" "io/ioutil" "log" "os" @@ -153,10 +152,10 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { sizeswg.Add(1) go func() { - var sizes types.Sizes - sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner) - // types.SizesFor always returns nil or a *types.StdSizes. - response.dr.Sizes, _ = sizes.(*types.StdSizes) + compiler, arch, err := packagesdriver.GetSizesForArgsGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner) + sizeserr = err + response.dr.Compiler = compiler + response.dr.Arch = arch sizeswg.Done() }() } diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index 632be722a2..124a6fe143 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -220,8 +220,10 @@ type driverResponse struct { // lists of multiple drivers, go/packages will fall back to the next driver. NotHandled bool - // Sizes, if not nil, is the types.Sizes to use when type checking. - Sizes *types.StdSizes + // Compiler and Arch are the arguments pass of types.SizesFor + // to get a types.Sizes to use when type checking. + Compiler string + Arch string // Roots is the set of package IDs that make up the root packages. // We have to encode this separately because when we encode a single package @@ -262,7 +264,7 @@ func Load(cfg *Config, patterns ...string) ([]*Package, error) { if err != nil { return nil, err } - l.sizes = response.Sizes + l.sizes = types.SizesFor(response.Compiler, response.Arch) return l.refine(response) } @@ -630,7 +632,7 @@ func newLoader(cfg *Config) *loader { return ld } -// refine connects the supplied packages into a graph and then adds type and +// refine connects the supplied packages into a graph and then adds type // and syntax information as requested by the LoadMode. func (ld *loader) refine(response *driverResponse) ([]*Package, error) { roots := response.Roots @@ -1043,6 +1045,9 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { Error: appendError, Sizes: ld.sizes, } + if lpkg.Module != nil && lpkg.Module.GoVersion != "" { + typesinternal.SetGoVersion(tc, "go"+lpkg.Module.GoVersion) + } if (ld.Mode & typecheckCgo) != 0 { if !typesinternal.SetUsesCgo(tc) { appendError(Error{ diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go new file mode 100644 index 0000000000..fa5834baf7 --- /dev/null +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -0,0 +1,827 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package objectpath defines a naming scheme for types.Objects +// (that is, named entities in Go programs) relative to their enclosing +// package. +// +// Type-checker objects are canonical, so they are usually identified by +// their address in memory (a pointer), but a pointer has meaning only +// within one address space. By contrast, objectpath names allow the +// identity of an object to be sent from one program to another, +// establishing a correspondence between types.Object variables that are +// distinct but logically equivalent. +// +// A single object may have multiple paths. In this example, +// +// type A struct{ X int } +// type B A +// +// the field X has two paths due to its membership of both A and B. +// The For(obj) function always returns one of these paths, arbitrarily +// but consistently. +package objectpath + +import ( + "fmt" + "go/types" + "sort" + "strconv" + "strings" + _ "unsafe" + + "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" +) + +// A Path is an opaque name that identifies a types.Object +// relative to its package. Conceptually, the name consists of a +// sequence of destructuring operations applied to the package scope +// to obtain the original object. +// The name does not include the package itself. +type Path string + +// Encoding +// +// An object path is a textual and (with training) human-readable encoding +// of a sequence of destructuring operators, starting from a types.Package. +// The sequences represent a path through the package/object/type graph. +// We classify these operators by their type: +// +// PO package->object Package.Scope.Lookup +// OT object->type Object.Type +// TT type->type Type.{Elem,Key,Params,Results,Underlying} [EKPRU] +// TO type->object Type.{At,Field,Method,Obj} [AFMO] +// +// All valid paths start with a package and end at an object +// and thus may be defined by the regular language: +// +// objectpath = PO (OT TT* TO)* +// +// The concrete encoding follows directly: +// - The only PO operator is Package.Scope.Lookup, which requires an identifier. +// - The only OT operator is Object.Type, +// which we encode as '.' because dot cannot appear in an identifier. +// - The TT operators are encoded as [EKPRUTC]; +// one of these (TypeParam) requires an integer operand, +// which is encoded as a string of decimal digits. +// - The TO operators are encoded as [AFMO]; +// three of these (At,Field,Method) require an integer operand, +// which is encoded as a string of decimal digits. +// These indices are stable across different representations +// of the same package, even source and export data. +// The indices used are implementation specific and may not correspond to +// the argument to the go/types function. +// +// In the example below, +// +// package p +// +// type T interface { +// f() (a string, b struct{ X int }) +// } +// +// field X has the path "T.UM0.RA1.F0", +// representing the following sequence of operations: +// +// p.Lookup("T") T +// .Type().Underlying().Method(0). f +// .Type().Results().At(1) b +// .Type().Field(0) X +// +// The encoding is not maximally compact---every R or P is +// followed by an A, for example---but this simplifies the +// encoder and decoder. +const ( + // object->type operators + opType = '.' // .Type() (Object) + + // type->type operators + opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map) + opKey = 'K' // .Key() (Map) + opParams = 'P' // .Params() (Signature) + opResults = 'R' // .Results() (Signature) + opUnderlying = 'U' // .Underlying() (Named) + opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature) + opConstraint = 'C' // .Constraint() (TypeParam) + + // type->object operators + opAt = 'A' // .At(i) (Tuple) + opField = 'F' // .Field(i) (Struct) + opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored) + opObj = 'O' // .Obj() (Named, TypeParam) +) + +// For is equivalent to new(Encoder).For(obj). +// +// It may be more efficient to reuse a single Encoder across several calls. +func For(obj types.Object) (Path, error) { + return new(Encoder).For(obj) +} + +// An Encoder amortizes the cost of encoding the paths of multiple objects. +// The zero value of an Encoder is ready to use. +type Encoder struct { + scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects + namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods() + skipMethodSorting bool +} + +// Expose back doors so that gopls can avoid method sorting, which can dominate +// analysis on certain repositories. +// +// TODO(golang/go#61443): remove this. +func init() { + typesinternal.SkipEncoderMethodSorting = func(enc interface{}) { + enc.(*Encoder).skipMethodSorting = true + } + typesinternal.ObjectpathObject = object +} + +// For returns the path to an object relative to its package, +// or an error if the object is not accessible from the package's Scope. +// +// The For function guarantees to return a path only for the following objects: +// - package-level types +// - exported package-level non-types +// - methods +// - parameter and result variables +// - struct fields +// These objects are sufficient to define the API of their package. +// The objects described by a package's export data are drawn from this set. +// +// The set of objects accessible from a package's Scope depends on +// whether the package was produced by type-checking syntax, or +// reading export data; the latter may have a smaller Scope since +// export data trims objects that are not reachable from an exported +// declaration. For example, the For function will return a path for +// an exported method of an unexported type that is not reachable +// from any public declaration; this path will cause the Object +// function to fail if called on a package loaded from export data. +// TODO(adonovan): is this a bug or feature? Should this package +// compute accessibility in the same way? +// +// For does not return a path for predeclared names, imported package +// names, local names, and unexported package-level names (except +// types). +// +// Example: given this definition, +// +// package p +// +// type T interface { +// f() (a string, b struct{ X int }) +// } +// +// For(X) would return a path that denotes the following sequence of operations: +// +// p.Scope().Lookup("T") (TypeName T) +// .Type().Underlying().Method(0). (method Func f) +// .Type().Results().At(1) (field Var b) +// .Type().Field(0) (field Var X) +// +// where p is the package (*types.Package) to which X belongs. +func (enc *Encoder) For(obj types.Object) (Path, error) { + pkg := obj.Pkg() + + // This table lists the cases of interest. + // + // Object Action + // ------ ------ + // nil reject + // builtin reject + // pkgname reject + // label reject + // var + // package-level accept + // func param/result accept + // local reject + // struct field accept + // const + // package-level accept + // local reject + // func + // package-level accept + // init functions reject + // concrete method accept + // interface method accept + // type + // package-level accept + // local reject + // + // The only accessible package-level objects are members of pkg itself. + // + // The cases are handled in four steps: + // + // 1. reject nil and builtin + // 2. accept package-level objects + // 3. reject obviously invalid objects + // 4. search the API for the path to the param/result/field/method. + + // 1. reference to nil or builtin? + if pkg == nil { + return "", fmt.Errorf("predeclared %s has no path", obj) + } + scope := pkg.Scope() + + // 2. package-level object? + if scope.Lookup(obj.Name()) == obj { + // Only exported objects (and non-exported types) have a path. + // Non-exported types may be referenced by other objects. + if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() { + return "", fmt.Errorf("no path for non-exported %v", obj) + } + return Path(obj.Name()), nil + } + + // 3. Not a package-level object. + // Reject obviously non-viable cases. + switch obj := obj.(type) { + case *types.TypeName: + if _, ok := obj.Type().(*typeparams.TypeParam); !ok { + // With the exception of type parameters, only package-level type names + // have a path. + return "", fmt.Errorf("no path for %v", obj) + } + case *types.Const, // Only package-level constants have a path. + *types.Label, // Labels are function-local. + *types.PkgName: // PkgNames are file-local. + return "", fmt.Errorf("no path for %v", obj) + + case *types.Var: + // Could be: + // - a field (obj.IsField()) + // - a func parameter or result + // - a local var. + // Sadly there is no way to distinguish + // a param/result from a local + // so we must proceed to the find. + + case *types.Func: + // A func, if not package-level, must be a method. + if recv := obj.Type().(*types.Signature).Recv(); recv == nil { + return "", fmt.Errorf("func is not a method: %v", obj) + } + + if path, ok := enc.concreteMethod(obj); ok { + // Fast path for concrete methods that avoids looping over scope. + return path, nil + } + + default: + panic(obj) + } + + // 4. Search the API for the path to the var (field/param/result) or method. + + // First inspect package-level named types. + // In the presence of path aliases, these give + // the best paths because non-types may + // refer to types, but not the reverse. + empty := make([]byte, 0, 48) // initial space + objs := enc.scopeObjects(scope) + for _, o := range objs { + tname, ok := o.(*types.TypeName) + if !ok { + continue // handle non-types in second pass + } + + path := append(empty, o.Name()...) + path = append(path, opType) + + T := o.Type() + + if tname.IsAlias() { + // type alias + if r := find(obj, T, path, nil); r != nil { + return Path(r), nil + } + } else { + if named, _ := T.(*types.Named); named != nil { + if r := findTypeParam(obj, typeparams.ForNamed(named), path, nil); r != nil { + // generic named type + return Path(r), nil + } + } + // defined (named) type + if r := find(obj, T.Underlying(), append(path, opUnderlying), nil); r != nil { + return Path(r), nil + } + } + } + + // Then inspect everything else: + // non-types, and declared methods of defined types. + for _, o := range objs { + path := append(empty, o.Name()...) + if _, ok := o.(*types.TypeName); !ok { + if o.Exported() { + // exported non-type (const, var, func) + if r := find(obj, o.Type(), append(path, opType), nil); r != nil { + return Path(r), nil + } + } + continue + } + + // Inspect declared methods of defined types. + if T, ok := o.Type().(*types.Named); ok { + path = append(path, opType) + if !enc.skipMethodSorting { + // Note that method index here is always with respect + // to canonical ordering of methods, regardless of how + // they appear in the underlying type. + for i, m := range enc.namedMethods(T) { + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return Path(path2), nil // found declared method + } + if r := find(obj, m.Type(), append(path2, opType), nil); r != nil { + return Path(r), nil + } + } + } else { + // This branch must match the logic in the branch above, using go/types + // APIs without sorting. + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return Path(path2), nil // found declared method + } + if r := find(obj, m.Type(), append(path2, opType), nil); r != nil { + return Path(r), nil + } + } + } + } + } + + return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path()) +} + +func appendOpArg(path []byte, op byte, arg int) []byte { + path = append(path, op) + path = strconv.AppendInt(path, int64(arg), 10) + return path +} + +// concreteMethod returns the path for meth, which must have a non-nil receiver. +// The second return value indicates success and may be false if the method is +// an interface method or if it is an instantiated method. +// +// This function is just an optimization that avoids the general scope walking +// approach. You are expected to fall back to the general approach if this +// function fails. +func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) { + // Concrete methods can only be declared on package-scoped named types. For + // that reason we can skip the expensive walk over the package scope: the + // path will always be package -> named type -> method. We can trivially get + // the type name from the receiver, and only have to look over the type's + // methods to find the method index. + // + // Methods on generic types require special consideration, however. Consider + // the following package: + // + // L1: type S[T any] struct{} + // L2: func (recv S[A]) Foo() { recv.Bar() } + // L3: func (recv S[B]) Bar() { } + // L4: type Alias = S[int] + // L5: func _[T any]() { var s S[int]; s.Foo() } + // + // The receivers of methods on generic types are instantiations. L2 and L3 + // instantiate S with the type-parameters A and B, which are scoped to the + // respective methods. L4 and L5 each instantiate S with int. Each of these + // instantiations has its own method set, full of methods (and thus objects) + // with receivers whose types are the respective instantiations. In other + // words, we have + // + // S[A].Foo, S[A].Bar + // S[B].Foo, S[B].Bar + // S[int].Foo, S[int].Bar + // + // We may thus be trying to produce object paths for any of these objects. + // + // S[A].Foo and S[B].Bar are the origin methods, and their paths are S.Foo + // and S.Bar, which are the paths that this function naturally produces. + // + // S[A].Bar, S[B].Foo, and both methods on S[int] are instantiations that + // don't correspond to the origin methods. For S[int], this is significant. + // The most precise object path for S[int].Foo, for example, is Alias.Foo, + // not S.Foo. Our function, however, would produce S.Foo, which would + // resolve to a different object. + // + // For S[A].Bar and S[B].Foo it could be argued that S.Bar and S.Foo are + // still the correct paths, since only the origin methods have meaningful + // paths. But this is likely only true for trivial cases and has edge cases. + // Since this function is only an optimization, we err on the side of giving + // up, deferring to the slower but definitely correct algorithm. Most users + // of objectpath will only be giving us origin methods, anyway, as referring + // to instantiated methods is usually not useful. + + if typeparams.OriginMethod(meth) != meth { + return "", false + } + + recvT := meth.Type().(*types.Signature).Recv().Type() + if ptr, ok := recvT.(*types.Pointer); ok { + recvT = ptr.Elem() + } + + named, ok := recvT.(*types.Named) + if !ok { + return "", false + } + + if types.IsInterface(named) { + // Named interfaces don't have to be package-scoped + // + // TODO(dominikh): opt: if scope.Lookup(name) == named, then we can apply this optimization to interface + // methods, too, I think. + return "", false + } + + // Preallocate space for the name, opType, opMethod, and some digits. + name := named.Obj().Name() + path := make([]byte, 0, len(name)+8) + path = append(path, name...) + path = append(path, opType) + + if !enc.skipMethodSorting { + for i, m := range enc.namedMethods(named) { + if m == meth { + path = appendOpArg(path, opMethod, i) + return Path(path), true + } + } + } else { + // This branch must match the logic of the branch above, using go/types + // APIs without sorting. + for i := 0; i < named.NumMethods(); i++ { + m := named.Method(i) + if m == meth { + path = appendOpArg(path, opMethod, i) + return Path(path), true + } + } + } + + // Due to golang/go#59944, go/types fails to associate the receiver with + // certain methods on cgo types. + // + // TODO(rfindley): replace this panic once golang/go#59944 is fixed in all Go + // versions gopls supports. + return "", false + // panic(fmt.Sprintf("couldn't find method %s on type %s; methods: %#v", meth, named, enc.namedMethods(named))) +} + +// find finds obj within type T, returning the path to it, or nil if not found. +// +// The seen map is used to short circuit cycles through type parameters. If +// nil, it will be allocated as necessary. +func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte { + switch T := T.(type) { + case *types.Basic, *types.Named: + // Named types belonging to pkg were handled already, + // so T must belong to another package. No path. + return nil + case *types.Pointer: + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Slice: + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Array: + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Chan: + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Map: + if r := find(obj, T.Key(), append(path, opKey), seen); r != nil { + return r + } + return find(obj, T.Elem(), append(path, opElem), seen) + case *types.Signature: + if r := findTypeParam(obj, typeparams.ForSignature(T), path, seen); r != nil { + return r + } + if r := find(obj, T.Params(), append(path, opParams), seen); r != nil { + return r + } + return find(obj, T.Results(), append(path, opResults), seen) + case *types.Struct: + for i := 0; i < T.NumFields(); i++ { + fld := T.Field(i) + path2 := appendOpArg(path, opField, i) + if fld == obj { + return path2 // found field var + } + if r := find(obj, fld.Type(), append(path2, opType), seen); r != nil { + return r + } + } + return nil + case *types.Tuple: + for i := 0; i < T.Len(); i++ { + v := T.At(i) + path2 := appendOpArg(path, opAt, i) + if v == obj { + return path2 // found param/result var + } + if r := find(obj, v.Type(), append(path2, opType), seen); r != nil { + return r + } + } + return nil + case *types.Interface: + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + path2 := appendOpArg(path, opMethod, i) + if m == obj { + return path2 // found interface method + } + if r := find(obj, m.Type(), append(path2, opType), seen); r != nil { + return r + } + } + return nil + case *typeparams.TypeParam: + name := T.Obj() + if name == obj { + return append(path, opObj) + } + if seen[name] { + return nil + } + if seen == nil { + seen = make(map[*types.TypeName]bool) + } + seen[name] = true + if r := find(obj, T.Constraint(), append(path, opConstraint), seen); r != nil { + return r + } + return nil + } + panic(T) +} + +func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte { + for i := 0; i < list.Len(); i++ { + tparam := list.At(i) + path2 := appendOpArg(path, opTypeParam, i) + if r := find(obj, tparam, path2, seen); r != nil { + return r + } + } + return nil +} + +// Object returns the object denoted by path p within the package pkg. +func Object(pkg *types.Package, p Path) (types.Object, error) { + return object(pkg, string(p), false) +} + +// Note: the skipMethodSorting parameter must match the value of +// Encoder.skipMethodSorting used during encoding. +func object(pkg *types.Package, pathstr string, skipMethodSorting bool) (types.Object, error) { + if pathstr == "" { + return nil, fmt.Errorf("empty path") + } + + var pkgobj, suffix string + if dot := strings.IndexByte(pathstr, opType); dot < 0 { + pkgobj = pathstr + } else { + pkgobj = pathstr[:dot] + suffix = pathstr[dot:] // suffix starts with "." + } + + obj := pkg.Scope().Lookup(pkgobj) + if obj == nil { + return nil, fmt.Errorf("package %s does not contain %q", pkg.Path(), pkgobj) + } + + // abstraction of *types.{Pointer,Slice,Array,Chan,Map} + type hasElem interface { + Elem() types.Type + } + // abstraction of *types.{Named,Signature} + type hasTypeParams interface { + TypeParams() *typeparams.TypeParamList + } + // abstraction of *types.{Named,TypeParam} + type hasObj interface { + Obj() *types.TypeName + } + + // The loop state is the pair (t, obj), + // exactly one of which is non-nil, initially obj. + // All suffixes start with '.' (the only object->type operation), + // followed by optional type->type operations, + // then a type->object operation. + // The cycle then repeats. + var t types.Type + for suffix != "" { + code := suffix[0] + suffix = suffix[1:] + + // Codes [AFM] have an integer operand. + var index int + switch code { + case opAt, opField, opMethod, opTypeParam: + rest := strings.TrimLeft(suffix, "0123456789") + numerals := suffix[:len(suffix)-len(rest)] + suffix = rest + i, err := strconv.Atoi(numerals) + if err != nil { + return nil, fmt.Errorf("invalid path: bad numeric operand %q for code %q", numerals, code) + } + index = int(i) + case opObj: + // no operand + default: + // The suffix must end with a type->object operation. + if suffix == "" { + return nil, fmt.Errorf("invalid path: ends with %q, want [AFMO]", code) + } + } + + if code == opType { + if t != nil { + return nil, fmt.Errorf("invalid path: unexpected %q in type context", opType) + } + t = obj.Type() + obj = nil + continue + } + + if t == nil { + return nil, fmt.Errorf("invalid path: code %q in object context", code) + } + + // Inv: t != nil, obj == nil + + switch code { + case opElem: + hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want pointer, slice, array, chan or map)", code, t, t) + } + t = hasElem.Elem() + + case opKey: + mapType, ok := t.(*types.Map) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want map)", code, t, t) + } + t = mapType.Key() + + case opParams: + sig, ok := t.(*types.Signature) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t) + } + t = sig.Params() + + case opResults: + sig, ok := t.(*types.Signature) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t) + } + t = sig.Results() + + case opUnderlying: + named, ok := t.(*types.Named) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named)", code, t, t) + } + t = named.Underlying() + + case opTypeParam: + hasTypeParams, ok := t.(hasTypeParams) // Named, Signature + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or signature)", code, t, t) + } + tparams := hasTypeParams.TypeParams() + if n := tparams.Len(); index >= n { + return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + } + t = tparams.At(index) + + case opConstraint: + tparam, ok := t.(*typeparams.TypeParam) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t) + } + t = tparam.Constraint() + + case opAt: + tuple, ok := t.(*types.Tuple) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want tuple)", code, t, t) + } + if n := tuple.Len(); index >= n { + return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + } + obj = tuple.At(index) + t = nil + + case opField: + structType, ok := t.(*types.Struct) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want struct)", code, t, t) + } + if n := structType.NumFields(); index >= n { + return nil, fmt.Errorf("field index %d out of range [0-%d)", index, n) + } + obj = structType.Field(index) + t = nil + + case opMethod: + switch t := t.(type) { + case *types.Interface: + if index >= t.NumMethods() { + return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods()) + } + obj = t.Method(index) // Id-ordered + + case *types.Named: + if index >= t.NumMethods() { + return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods()) + } + if skipMethodSorting { + obj = t.Method(index) + } else { + methods := namedMethods(t) // (unmemoized) + obj = methods[index] // Id-ordered + } + + default: + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t) + } + t = nil + + case opObj: + hasObj, ok := t.(hasObj) + if !ok { + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or type param)", code, t, t) + } + obj = hasObj.Obj() + t = nil + + default: + return nil, fmt.Errorf("invalid path: unknown code %q", code) + } + } + + if obj.Pkg() != pkg { + return nil, fmt.Errorf("path denotes %s, which belongs to a different package", obj) + } + + return obj, nil // success +} + +// namedMethods returns the methods of a Named type in ascending Id order. +func namedMethods(named *types.Named) []*types.Func { + methods := make([]*types.Func, named.NumMethods()) + for i := range methods { + methods[i] = named.Method(i) + } + sort.Slice(methods, func(i, j int) bool { + return methods[i].Id() < methods[j].Id() + }) + return methods +} + +// namedMethods is a memoization of the namedMethods function. Callers must not modify the result. +func (enc *Encoder) namedMethods(named *types.Named) []*types.Func { + m := enc.namedMethodsMemo + if m == nil { + m = make(map[*types.Named][]*types.Func) + enc.namedMethodsMemo = m + } + methods, ok := m[named] + if !ok { + methods = namedMethods(named) // allocates and sorts + m[named] = methods + } + return methods +} + +// scopeObjects is a memoization of scope objects. +// Callers must not modify the result. +func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object { + m := enc.scopeMemo + if m == nil { + m = make(map[*types.Scope][]types.Object) + enc.scopeMemo = m + } + objs, ok := m[scope] + if !ok { + names := scope.Names() // allocates and sorts + objs = make([]types.Object, len(names)) + for i, name := range names { + objs[i] = scope.Lookup(name) + } + m[scope] = objs + } + return objs +} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index eed1702186..6103dd7102 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -22,17 +22,23 @@ import ( "strconv" "strings" + "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/internal/tokeninternal" "golang.org/x/tools/internal/typeparams" ) // IExportShallow encodes "shallow" export data for the specified package. // -// No promises are made about the encoding other than that it can be -// decoded by the same version of IIExportShallow. If you plan to save -// export data in the file system, be sure to include a cryptographic -// digest of the executable in the key to avoid version skew. -func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) { +// No promises are made about the encoding other than that it can be decoded by +// the same version of IIExportShallow. If you plan to save export data in the +// file system, be sure to include a cryptographic digest of the executable in +// the key to avoid version skew. +// +// If the provided reportf func is non-nil, it will be used for reporting bugs +// encountered during export. +// TODO(rfindley): remove reportf when we are confident enough in the new +// objectpath encoding. +func IExportShallow(fset *token.FileSet, pkg *types.Package, reportf ReportFunc) ([]byte, error) { // In principle this operation can only fail if out.Write fails, // but that's impossible for bytes.Buffer---and as a matter of // fact iexportCommon doesn't even check for I/O errors. @@ -51,16 +57,24 @@ func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) { // The importer calls getPackages to obtain package symbols for all // packages mentioned in the export data, including the one being // decoded. -func IImportShallow(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, path string) (*types.Package, error) { +// +// If the provided reportf func is non-nil, it will be used for reporting bugs +// encountered during import. +// TODO(rfindley): remove reportf when we are confident enough in the new +// objectpath encoding. +func IImportShallow(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, path string, reportf ReportFunc) (*types.Package, error) { const bundle = false const shallow = true - pkgs, err := iimportCommon(fset, getPackages, data, bundle, path, shallow) + pkgs, err := iimportCommon(fset, getPackages, data, bundle, path, shallow, reportf) if err != nil { return nil, err } return pkgs[0], nil } +// ReportFunc is the type of a function used to report formatted bugs. +type ReportFunc = func(string, ...interface{}) + // Current bundled export format version. Increase with each format change. // 0: initial implementation const bundleVersion = 0 @@ -313,8 +327,9 @@ type iexporter struct { out *bytes.Buffer version int - shallow bool // don't put types from other packages in the index - localpkg *types.Package // (nil in bundle mode) + shallow bool // don't put types from other packages in the index + objEncoder *objectpath.Encoder // encodes objects from other packages in shallow mode; lazily allocated + localpkg *types.Package // (nil in bundle mode) // allPkgs tracks all packages that have been referenced by // the export data, so we can ensure to include them in the @@ -354,6 +369,17 @@ func (p *iexporter) trace(format string, args ...interface{}) { fmt.Printf(strings.Repeat("..", p.indent)+format+"\n", args...) } +// objectpathEncoder returns the lazily allocated objectpath.Encoder to use +// when encoding objects in other packages during shallow export. +// +// Using a shared Encoder amortizes some of cost of objectpath search. +func (p *iexporter) objectpathEncoder() *objectpath.Encoder { + if p.objEncoder == nil { + p.objEncoder = new(objectpath.Encoder) + } + return p.objEncoder +} + // stringOff returns the offset of s within the string section. // If not already present, it's added to the end. func (p *iexporter) stringOff(s string) uint64 { @@ -413,7 +439,6 @@ type exportWriter struct { p *iexporter data intWriter - currPkg *types.Package prevFile string prevLine int64 prevColumn int64 @@ -436,7 +461,6 @@ func (p *iexporter) doDecl(obj types.Object) { }() } w := p.newWriter() - w.setPkg(obj.Pkg(), false) switch obj := obj.(type) { case *types.Var: @@ -767,15 +791,19 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { case *types.Signature: w.startType(signatureType) - w.setPkg(pkg, true) + w.pkg(pkg) w.signature(t) case *types.Struct: w.startType(structType) n := t.NumFields() + // Even for struct{} we must emit some qualifying package, because that's + // what the compiler does, and thus that's what the importer expects. + fieldPkg := pkg if n > 0 { - w.setPkg(t.Field(0).Pkg(), true) // qualifying package for field objects - } else { + fieldPkg = t.Field(0).Pkg() + } + if fieldPkg == nil { // TODO(rfindley): improve this very hacky logic. // // The importer expects a package to be set for all struct types, even @@ -783,28 +811,33 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { // before pkg. setPkg panics with a nil package, which may be possible // to reach with invalid packages (and perhaps valid packages, too?), so // (arbitrarily) set the localpkg if available. - switch { - case pkg != nil: - w.setPkg(pkg, true) - case w.p.shallow: - w.setPkg(w.p.localpkg, true) - default: + // + // Alternatively, we may be able to simply guarantee that pkg != nil, by + // reconsidering the encoding of constant values. + if w.p.shallow { + fieldPkg = w.p.localpkg + } else { panic(internalErrorf("no package to set for empty struct")) } } + w.pkg(fieldPkg) w.uint64(uint64(n)) + for i := 0; i < n; i++ { f := t.Field(i) + if w.p.shallow { + w.objectPath(f) + } w.pos(f.Pos()) w.string(f.Name()) // unexported fields implicitly qualified by prior setPkg - w.typ(f.Type(), pkg) + w.typ(f.Type(), fieldPkg) w.bool(f.Anonymous()) w.string(t.Tag(i)) // note (or tag) } case *types.Interface: w.startType(interfaceType) - w.setPkg(pkg, true) + w.pkg(pkg) n := t.NumEmbeddeds() w.uint64(uint64(n)) @@ -819,10 +852,16 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { w.typ(ft, tPkg) } + // See comment for struct fields. In shallow mode we change the encoding + // for interface methods that are promoted from other packages. + n = t.NumExplicitMethods() w.uint64(uint64(n)) for i := 0; i < n; i++ { m := t.ExplicitMethod(i) + if w.p.shallow { + w.objectPath(m) + } w.pos(m.Pos()) w.string(m.Name()) sig, _ := m.Type().(*types.Signature) @@ -844,12 +883,61 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { } } -func (w *exportWriter) setPkg(pkg *types.Package, write bool) { - if write { - w.pkg(pkg) +// objectPath writes the package and objectPath to use to look up obj in a +// different package, when encoding in "shallow" mode. +// +// When doing a shallow import, the importer creates only the local package, +// and requests package symbols for dependencies from the client. +// However, certain types defined in the local package may hold objects defined +// (perhaps deeply) within another package. +// +// For example, consider the following: +// +// package a +// func F() chan * map[string] struct { X int } +// +// package b +// import "a" +// var B = a.F() +// +// In this example, the type of b.B holds fields defined in package a. +// In order to have the correct canonical objects for the field defined in the +// type of B, they are encoded as objectPaths and later looked up in the +// importer. The same problem applies to interface methods. +func (w *exportWriter) objectPath(obj types.Object) { + if obj.Pkg() == nil || obj.Pkg() == w.p.localpkg { + // obj.Pkg() may be nil for the builtin error.Error. + // In this case, or if obj is declared in the local package, no need to + // encode. + w.string("") + return } - - w.currPkg = pkg + objectPath, err := w.p.objectpathEncoder().For(obj) + if err != nil { + // Fall back to the empty string, which will cause the importer to create a + // new object, which matches earlier behavior. Creating a new object is + // sufficient for many purposes (such as type checking), but causes certain + // references algorithms to fail (golang/go#60819). However, we didn't + // notice this problem during months of gopls@v0.12.0 testing. + // + // TODO(golang/go#61674): this workaround is insufficient, as in the case + // where the field forwarded from an instantiated type that may not appear + // in the export data of the original package: + // + // // package a + // type A[P any] struct{ F P } + // + // // package b + // type B a.A[int] + // + // We need to update references algorithms not to depend on this + // de-duplication, at which point we may want to simply remove the + // workaround here. + w.string("") + return + } + w.string(string(objectPath)) + w.pkg(obj.Pkg()) } func (w *exportWriter) signature(sig *types.Signature) { diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index fb6554f926..8e64cf644f 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -21,6 +21,7 @@ import ( "sort" "strings" + "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/internal/typeparams" ) @@ -85,7 +86,7 @@ const ( // If the export data version is not recognized or the format is otherwise // compromised, an error is returned. func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) { - pkgs, err := iimportCommon(fset, GetPackagesFromMap(imports), data, false, path, false) + pkgs, err := iimportCommon(fset, GetPackagesFromMap(imports), data, false, path, false, nil) if err != nil { return 0, nil, err } @@ -94,7 +95,7 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data [] // IImportBundle imports a set of packages from the serialized package bundle. func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data []byte) ([]*types.Package, error) { - return iimportCommon(fset, GetPackagesFromMap(imports), data, true, "", false) + return iimportCommon(fset, GetPackagesFromMap(imports), data, true, "", false, nil) } // A GetPackagesFunc function obtains the non-nil symbols for a set of @@ -136,7 +137,7 @@ func GetPackagesFromMap(m map[string]*types.Package) GetPackagesFunc { } } -func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, bundle bool, path string, shallow bool) (pkgs []*types.Package, err error) { +func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, bundle bool, path string, shallow bool, reportf ReportFunc) (pkgs []*types.Package, err error) { const currentVersion = iexportVersionCurrent version := int64(-1) if !debug { @@ -192,9 +193,10 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte r.Seek(sLen+fLen+dLen, io.SeekCurrent) p := iimporter{ - version: int(version), - ipath: path, - usePosv2: shallow, // precise offsets are encoded only in shallow mode + version: int(version), + ipath: path, + shallow: shallow, + reportf: reportf, stringData: stringData, stringCache: make(map[uint64]string), @@ -326,6 +328,13 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte typ.Complete() } + // Workaround for golang/go#61561. See the doc for instanceList for details. + for _, typ := range p.instanceList { + if iface, _ := typ.Underlying().(*types.Interface); iface != nil { + iface.Complete() + } + } + return pkgs, nil } @@ -338,7 +347,8 @@ type iimporter struct { version int ipath string - usePosv2 bool + shallow bool + reportf ReportFunc // if non-nil, used to report bugs stringData []byte stringCache map[uint64]string @@ -355,6 +365,12 @@ type iimporter struct { fake fakeFileSet interfaceList []*types.Interface + // Workaround for the go/types bug golang/go#61561: instances produced during + // instantiation may contain incomplete interfaces. Here we only complete the + // underlying type of the instance, which is the most common case but doesn't + // handle parameterized interface literals defined deeper in the type. + instanceList []types.Type // instances for later completion (see golang/go#61561) + // Arguments for calls to SetConstraint that are deferred due to recursive types later []setConstraintArgs @@ -755,7 +771,8 @@ func (r *importReader) qualifiedIdent() (*types.Package, string) { } func (r *importReader) pos() token.Pos { - if r.p.usePosv2 { + if r.p.shallow { + // precise offsets are encoded only in shallow mode return r.posv2() } if r.p.version >= iexportVersionPosCol { @@ -856,13 +873,28 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { fields := make([]*types.Var, r.uint64()) tags := make([]string, len(fields)) for i := range fields { + var field *types.Var + if r.p.shallow { + field, _ = r.objectPathObject().(*types.Var) + } + fpos := r.pos() fname := r.ident() ftyp := r.typ() emb := r.bool() tag := r.string() - fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb) + // Either this is not a shallow import, the field is local, or the + // encoded objectPath failed to produce an object (a bug). + // + // Even in this last, buggy case, fall back on creating a new field. As + // discussed in iexport.go, this is not correct, but mostly works and is + // preferable to failing (for now at least). + if field == nil { + field = types.NewField(fpos, r.currPkg, fname, ftyp, emb) + } + + fields[i] = field tags[i] = tag } return types.NewStruct(fields, tags) @@ -878,6 +910,11 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { methods := make([]*types.Func, r.uint64()) for i := range methods { + var method *types.Func + if r.p.shallow { + method, _ = r.objectPathObject().(*types.Func) + } + mpos := r.pos() mname := r.ident() @@ -887,9 +924,12 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { if base != nil { recv = types.NewVar(token.NoPos, r.currPkg, "", base) } - msig := r.signature(recv, nil, nil) - methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig) + + if method == nil { + method = types.NewFunc(mpos, r.currPkg, mname, msig) + } + methods[i] = method } typ := newInterface(methods, embeddeds) @@ -927,6 +967,9 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { // we must always use the methods of the base (orig) type. // TODO provide a non-nil *Environment t, _ := typeparams.Instantiate(nil, baseType, targs, false) + + // Workaround for golang/go#61561. See the doc for instanceList for details. + r.p.instanceList = append(r.p.instanceList, t) return t case unionType: @@ -945,6 +988,26 @@ func (r *importReader) kind() itag { return itag(r.uint64()) } +// objectPathObject is the inverse of exportWriter.objectPath. +// +// In shallow mode, certain fields and methods may need to be looked up in an +// imported package. See the doc for exportWriter.objectPath for a full +// explanation. +func (r *importReader) objectPathObject() types.Object { + objPath := objectpath.Path(r.string()) + if objPath == "" { + return nil + } + pkg := r.pkg() + obj, err := objectpath.Object(pkg, objPath) + if err != nil { + if r.p.reportf != nil { + r.p.reportf("failed to find object for objectPath %q: %v", objPath, err) + } + } + return obj +} + func (r *importReader) signature(recv *types.Var, rparams []*typeparams.TypeParam, tparams []*typeparams.TypeParam) *types.Signature { params := r.paramList() results := r.paramList() diff --git a/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/vendor/golang.org/x/tools/internal/gocommand/invoke.go index 8d9fc98d8f..53cf66da01 100644 --- a/vendor/golang.org/x/tools/internal/gocommand/invoke.go +++ b/vendor/golang.org/x/tools/internal/gocommand/invoke.go @@ -319,7 +319,7 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) (err error) { // Per https://pkg.go.dev/os#File.Close, the call to stdoutR.Close // should cause the Read call in io.Copy to unblock and return // immediately, but we still need to receive from stdoutErr to confirm - // that that has happened. + // that it has happened. <-stdoutErr err2 = ctx.Err() } @@ -333,7 +333,7 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) (err error) { // one goroutine at a time will call Write.” // // Since we're starting a goroutine that writes to cmd.Stdout, we must - // also update cmd.Stderr so that that still holds. + // also update cmd.Stderr so that it still holds. func() { defer func() { recover() }() if cmd.Stderr == prevStdout { diff --git a/vendor/golang.org/x/tools/internal/typeparams/common.go b/vendor/golang.org/x/tools/internal/typeparams/common.go index b9e87c691a..d0d0649fe2 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/common.go +++ b/vendor/golang.org/x/tools/internal/typeparams/common.go @@ -23,6 +23,7 @@ package typeparams import ( + "fmt" "go/ast" "go/token" "go/types" @@ -125,6 +126,11 @@ func OriginMethod(fn *types.Func) *types.Func { } } + // In golang/go#61196, we observe another crash, this time inexplicable. + if gfn == nil { + panic(fmt.Sprintf("missing origin method for %s.%s; named == origin: %t, named.NumMethods(): %d, origin.NumMethods(): %d", named, fn, named == orig, named.NumMethods(), orig.NumMethods())) + } + return gfn.(*types.Func) } diff --git a/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/vendor/golang.org/x/tools/internal/typeparams/coretype.go index 993135ec90..71248209ee 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/coretype.go +++ b/vendor/golang.org/x/tools/internal/typeparams/coretype.go @@ -81,13 +81,13 @@ func CoreType(T types.Type) types.Type { // restrictions may be arbitrarily complex. For example, consider the // following: // -// type A interface{ ~string|~[]byte } +// type A interface{ ~string|~[]byte } // -// type B interface{ int|string } +// type B interface{ int|string } // -// type C interface { ~string|~int } +// type C interface { ~string|~int } // -// type T[P interface{ A|B; C }] int +// type T[P interface{ A|B; C }] int // // In this example, the structural type restriction of P is ~string|int: A|B // expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, diff --git a/vendor/golang.org/x/tools/internal/typeparams/termlist.go b/vendor/golang.org/x/tools/internal/typeparams/termlist.go index 933106a23d..cbd12f8013 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/termlist.go +++ b/vendor/golang.org/x/tools/internal/typeparams/termlist.go @@ -30,7 +30,7 @@ func (xl termlist) String() string { var buf bytes.Buffer for i, x := range xl { if i > 0 { - buf.WriteString(" ∪ ") + buf.WriteString(" | ") } buf.WriteString(x.String()) } diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go index b4788978ff..7ed86e1711 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go +++ b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go @@ -129,7 +129,7 @@ func NamedTypeArgs(*types.Named) *TypeList { } // NamedTypeOrigin is the identity method at this Go version. -func NamedTypeOrigin(named *types.Named) types.Type { +func NamedTypeOrigin(named *types.Named) *types.Named { return named } diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go index 114a36b866..cf301af1db 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go +++ b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go @@ -103,7 +103,7 @@ func NamedTypeArgs(named *types.Named) *TypeList { } // NamedTypeOrigin returns named.Orig(). -func NamedTypeOrigin(named *types.Named) types.Type { +func NamedTypeOrigin(named *types.Named) *types.Named { return named.Origin() } diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeterm.go b/vendor/golang.org/x/tools/internal/typeparams/typeterm.go index 7ddee28d98..7350bb702a 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/typeterm.go +++ b/vendor/golang.org/x/tools/internal/typeparams/typeterm.go @@ -10,11 +10,10 @@ import "go/types" // A term describes elementary type sets: // -// ∅: (*term)(nil) == ∅ // set of no types (empty set) -// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) -// T: &term{false, T} == {T} // set of type T -// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t -// +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t type term struct { tilde bool // valid if typ != nil typ types.Type diff --git a/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go b/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go new file mode 100644 index 0000000000..5e96e89557 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/objectpath.go @@ -0,0 +1,24 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import "go/types" + +// This file contains back doors that allow gopls to avoid method sorting when +// using the objectpath package. +// +// This is performance-critical in certain repositories, but changing the +// behavior of the objectpath package is still being discussed in +// golang/go#61443. If we decide to remove the sorting in objectpath we can +// simply delete these back doors. Otherwise, we should add a new API to +// objectpath that allows controlling the sorting. + +// SkipEncoderMethodSorting marks enc (which must be an *objectpath.Encoder) as +// not requiring sorted methods. +var SkipEncoderMethodSorting func(enc interface{}) + +// ObjectpathObject is like objectpath.Object, but allows suppressing method +// sorting. +var ObjectpathObject func(pkg *types.Package, p string, skipMethodSorting bool) (types.Object, error) diff --git a/vendor/modules.txt b/vendor/modules.txt index 074d62f392..4b40777280 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -688,8 +688,9 @@ github.com/pkg/errors # github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 ## explicit github.com/pmezard/go-difflib/difflib -# github.com/prometheus/alertmanager v0.26.0 +# github.com/prometheus/alertmanager v0.26.0 => github.com/emanlodovice/prometheus-alertmanager v0.0.0-20231011203108-c700916131f5 ## explicit; go 1.18 +github.com/prometheus/alertmanager/alertobserver github.com/prometheus/alertmanager/api github.com/prometheus/alertmanager/api/metrics github.com/prometheus/alertmanager/api/v1 @@ -707,6 +708,7 @@ github.com/prometheus/alertmanager/cluster github.com/prometheus/alertmanager/cluster/clusterpb github.com/prometheus/alertmanager/config github.com/prometheus/alertmanager/dispatch +github.com/prometheus/alertmanager/featurecontrol github.com/prometheus/alertmanager/inhibit github.com/prometheus/alertmanager/nflog github.com/prometheus/alertmanager/nflog/nflogpb @@ -734,6 +736,7 @@ github.com/prometheus/alertmanager/template github.com/prometheus/alertmanager/timeinterval github.com/prometheus/alertmanager/types github.com/prometheus/alertmanager/ui +github.com/prometheus/alertmanager/ui/react-app # github.com/prometheus/client_golang v1.16.0 ## explicit; go 1.17 github.com/prometheus/client_golang/api @@ -756,7 +759,11 @@ github.com/prometheus/common/expfmt github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg github.com/prometheus/common/model github.com/prometheus/common/route +github.com/prometheus/common/server github.com/prometheus/common/version +# github.com/prometheus/common/assets v0.2.0 +## explicit; go 1.17 +github.com/prometheus/common/assets # github.com/prometheus/common/sigv4 v0.1.0 ## explicit; go 1.15 github.com/prometheus/common/sigv4 @@ -829,7 +836,7 @@ github.com/prometheus/prometheus/web/api/v1 github.com/redis/rueidis github.com/redis/rueidis/internal/cmds github.com/redis/rueidis/internal/util -# github.com/rs/cors v1.9.0 +# github.com/rs/cors v1.10.1 ## explicit; go 1.13 github.com/rs/cors # github.com/rs/xid v1.5.0 @@ -1174,7 +1181,7 @@ go4.org/intern # go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 ## explicit; go 1.11 go4.org/unsafe/assume-no-moving-gc -# golang.org/x/crypto v0.12.0 +# golang.org/x/crypto v0.13.0 ## explicit; go 1.17 golang.org/x/crypto/argon2 golang.org/x/crypto/bcrypt @@ -1196,7 +1203,7 @@ golang.org/x/exp/slices # golang.org/x/mod v0.12.0 ## explicit; go 1.17 golang.org/x/mod/semver -# golang.org/x/net v0.14.0 +# golang.org/x/net v0.15.0 ## explicit; go 1.17 golang.org/x/net/bpf golang.org/x/net/context @@ -1235,7 +1242,7 @@ golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry -# golang.org/x/text v0.12.0 +# golang.org/x/text v0.13.0 ## explicit; go 1.17 golang.org/x/text/cases golang.org/x/text/internal @@ -1251,11 +1258,12 @@ golang.org/x/text/unicode/norm # golang.org/x/time v0.3.0 ## explicit golang.org/x/time/rate -# golang.org/x/tools v0.11.0 +# golang.org/x/tools v0.13.0 ## explicit; go 1.18 golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/internal/packagesdriver golang.org/x/tools/go/packages +golang.org/x/tools/go/types/objectpath golang.org/x/tools/internal/event golang.org/x/tools/internal/event/core golang.org/x/tools/internal/event/keys @@ -1456,3 +1464,4 @@ sigs.k8s.io/yaml # github.com/google/gnostic => github.com/googleapis/gnostic v0.6.9 # gopkg.in/alecthomas/kingpin.v2 => github.com/alecthomas/kingpin v1.3.8-0.20210301060133-17f40c25f497 # github.com/sercand/kuberesolver => github.com/sercand/kuberesolver/v4 v4.0.0 +# github.com/prometheus/alertmanager => github.com/emanlodovice/prometheus-alertmanager v0.0.0-20231011203108-c700916131f5 From b00cc53d424d91a224777d06847c2ccf43ee2fc2 Mon Sep 17 00:00:00 2001 From: Emmanuel Lodovice Date: Sun, 15 Oct 2023 18:44:37 -0700 Subject: [PATCH 2/3] Add support for alertmanager alert observer Signed-off-by: Emmanuel Lodovice --- pkg/alertmanager/alert_observer.go | 286 ++++++++++++++++++++++++ pkg/alertmanager/alert_observer_test.go | 249 +++++++++++++++++++++ pkg/alertmanager/alertmanager.go | 31 ++- pkg/alertmanager/alertmanager_test.go | 119 +++++++--- pkg/alertmanager/multitenant.go | 17 +- pkg/alertmanager/multitenant_test.go | 59 +++-- pkg/cortex/modules.go | 10 + pkg/util/validation/limits.go | 6 + 8 files changed, 725 insertions(+), 52 deletions(-) create mode 100644 pkg/alertmanager/alert_observer.go create mode 100644 pkg/alertmanager/alert_observer_test.go diff --git a/pkg/alertmanager/alert_observer.go b/pkg/alertmanager/alert_observer.go new file mode 100644 index 0000000000..b7cb7affcc --- /dev/null +++ b/pkg/alertmanager/alert_observer.go @@ -0,0 +1,286 @@ +package alertmanager + +import ( + "context" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/alertmanager/alertobserver" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/types" +) + +type ObserverLimits interface { + AlertmanagerAlertLifeCycleObserverLevel(tenant string) int +} + +type AlertLifeCycleObserverLimiter struct { + limits ObserverLimits + tenant string +} + +func NewAlertLifeCycleObserverLimiter(tenant string, limits ObserverLimits) *AlertLifeCycleObserverLimiter { + return &AlertLifeCycleObserverLimiter{ + tenant: tenant, + limits: limits, + } +} + +func (a *AlertLifeCycleObserverLimiter) Level() int { + return a.limits.AlertmanagerAlertLifeCycleObserverLevel(a.tenant) +} + +type LogAlertLifeCycleObserver struct { + logger log.Logger + limiter *AlertLifeCycleObserverLimiter +} + +func NewLogAlertLifeCycleObserver(logger log.Logger, user string, limiter *AlertLifeCycleObserverLimiter) *LogAlertLifeCycleObserver { + logger = log.With(logger, "user", user) + logger = log.With(logger, "component", "observer") + return &LogAlertLifeCycleObserver{ + logger: logger, + limiter: limiter, + } +} + +// Observe implements LifeCycleObserver +func (o *LogAlertLifeCycleObserver) Observe(event string, alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + if alerts == nil || o.limiter == nil || o.limiter.Level() <= 0 { + return + } + // The general idea of having levels in the limiter is to control the volume of logs AM is producing. The observer + // logs many types of events and some events are more important than others. By configuring the level we can + // continue to have observability on the alerts at lower granularity if we see that the volume of logs is getting + // too expensive. + // What is log per level is as follows: + //* 1 + // * Alert is rejected because of validation error + // * Alert joins an aggregation group + // * Alert is muted + // * Aggregation Group notification Sent / Failed + //* 2 + // * Alert is rejected because of validation error + // * Alert joins an aggregation group + // * Alert is muted + // * Aggregation Group pipeline start + // * Aggregation Group notification Sent / Failed + //* 3 + // * Alert is rejected because of validation error + // * Alert joins an aggregation group + // * Alert is muted + // * Aggregation Group pipeline start + // * Aggregation Group passed a stage in the pipeline + // * Aggregation Group notification Sent / Failed + //* 4 + // * Alert is rejected because of validation error + // * Alert joins an aggregation group + // * Alert is muted + // * Aggregation group pipeline start + // * Aggregation Group passed a stage in the pipeline + // * Alert in aggregation group is Sent / Failed + //* 5 + // * Alert is rejected because of validation error + // * Alert is received + // * Alert joins an aggregation group + // * Alert is muted + // * Alert in aggregation group pipeline start + // * Aggregation Group passed a stage in the pipeline + // * Alert in aggregation group is Sent / Failed + + switch event { + case alertobserver.EventAlertReceived: + o.Received(alerts) + case alertobserver.EventAlertRejected: + o.Rejected(alerts, meta) + case alertobserver.EventAlertAddedToAggrGroup: + o.AddedAggrGroup(alerts, meta) + case alertobserver.EventAlertFailedAddToAggrGroup: + o.FailedAddToAggrGroup(alerts, meta) + case alertobserver.EventAlertPipelineStart: + o.PipelineStart(alerts, meta) + case alertobserver.EventAlertPipelinePassStage: + o.PipelinePassStage(alerts, meta) + case alertobserver.EventAlertSent: + o.Sent(alerts, meta) + case alertobserver.EventAlertSendFailed: + o.SendFailed(alerts, meta) + case alertobserver.EventAlertMuted: + o.Muted(alerts, meta) + } +} + +func (o *LogAlertLifeCycleObserver) Received(alerts []*types.Alert) { + if o.limiter.Level() < 5 { + return + } + for _, a := range alerts { + o.logWithAlert(a, true, "msg", "Received") + } +} + +func (o *LogAlertLifeCycleObserver) Rejected(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + reason, ok := meta["msg"] + if !ok { + reason = "Unknown" + } + for _, a := range alerts { + o.logWithAlert(a, true, "msg", "Rejected", "reason", reason) + } +} + +func (o *LogAlertLifeCycleObserver) AddedAggrGroup(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + groupKey, ok := meta["groupKey"] + if !ok { + return + } + for _, a := range alerts { + o.logWithAlert(a, true, "msg", "Added to aggregation group", "groupKey", groupKey) + } +} + +func (o *LogAlertLifeCycleObserver) FailedAddToAggrGroup(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + reason, ok := meta["msg"] + if !ok { + reason = "Unknown" + } + for _, a := range alerts { + o.logWithAlert(a, true, "msg", "Failed to add aggregation group", "reason", reason) + } +} + +func (o *LogAlertLifeCycleObserver) PipelineStart(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + logLvl := o.limiter.Level() + if logLvl < 2 { + return + } + ctx, ok := meta["ctx"] + if !ok { + return + } + receiver, ok := notify.ReceiverName(ctx.(context.Context)) + if !ok { + return + } + groupKey, ok := notify.GroupKey(ctx.(context.Context)) + if !ok { + return + } + if logLvl < 5 { + level.Info(o.logger).Log("msg", "Entered the pipeline", "groupKey", groupKey, "receiver", receiver, "alertsCount", len(alerts)) + } else { + for _, a := range alerts { + o.logWithAlert(a, false, "msg", "Entered the pipeline", "groupKey", groupKey, "receiver", receiver) + } + } +} + +func (o *LogAlertLifeCycleObserver) PipelinePassStage(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + if o.limiter.Level() < 3 { + return + } + stageName, ok := meta["stageName"] + if !ok { + return + } + if stageName == "FanoutStage" { + // Fanout stage is just a collection of stages, so we don't really need to log it. We know if the pipeline + // enters the Fanout stage based on the logs of its substages + return + } + ctx, ok := meta["ctx"] + if !ok { + return + } + receiver, ok := notify.ReceiverName(ctx.(context.Context)) + if !ok { + return + } + groupKey, ok := notify.GroupKey(ctx.(context.Context)) + if !ok { + return + } + level.Info(o.logger).Log("msg", "Passed stage", "groupKey", groupKey, "receiver", receiver, "stage", stageName, "alertsCount", len(alerts)) +} + +func (o *LogAlertLifeCycleObserver) Sent(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + ctx, ok := meta["ctx"] + if !ok { + return + } + integration, ok := meta["integration"] + if !ok { + return + } + receiver, ok := notify.ReceiverName(ctx.(context.Context)) + if !ok { + return + } + groupKey, ok := notify.GroupKey(ctx.(context.Context)) + if !ok { + return + } + if o.limiter.Level() < 4 { + level.Info(o.logger).Log("msg", "Sent", "groupKey", groupKey, "receiver", receiver, "integration", integration, "alertsCount", len(alerts)) + } else { + for _, a := range alerts { + o.logWithAlert(a, false, "msg", "Sent", "groupKey", groupKey, "receiver", receiver, "integration", integration) + } + } +} + +func (o *LogAlertLifeCycleObserver) SendFailed(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + ctx, ok := meta["ctx"] + if !ok { + return + } + integration, ok := meta["integration"] + if !ok { + return + } + receiver, ok := notify.ReceiverName(ctx.(context.Context)) + if !ok { + return + } + groupKey, ok := notify.GroupKey(ctx.(context.Context)) + if !ok { + return + } + if o.limiter.Level() < 4 { + level.Info(o.logger).Log("msg", "Send failed", "groupKey", groupKey, "receiver", receiver, "integration", integration, "alertsCount", len(alerts)) + } else { + for _, a := range alerts { + o.logWithAlert(a, false, "msg", "Send failed", "groupKey", groupKey, "receiver", receiver, "integration", integration) + } + } +} + +func (o *LogAlertLifeCycleObserver) Muted(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { + ctx, ok := meta["ctx"] + if !ok { + return + } + groupKey, ok := notify.GroupKey(ctx.(context.Context)) + if !ok { + return + } + for _, a := range alerts { + o.logWithAlert(a, false, "msg", "Muted", "groupKey", groupKey) + } +} + +func (o *LogAlertLifeCycleObserver) logWithAlert(alert *types.Alert, addLabels bool, keyvals ...interface{}) { + keyvals = append( + keyvals, + "fingerprint", + alert.Fingerprint().String(), + "start", + alert.StartsAt.Unix(), + "end", + alert.EndsAt.Unix(), + ) + if addLabels { + keyvals = append(keyvals, "labels", alert.Labels.String()) + } + level.Info(o.logger).Log(keyvals...) +} diff --git a/pkg/alertmanager/alert_observer_test.go b/pkg/alertmanager/alert_observer_test.go new file mode 100644 index 0000000000..81cd23b532 --- /dev/null +++ b/pkg/alertmanager/alert_observer_test.go @@ -0,0 +1,249 @@ +package alertmanager + +import ( + "context" + "fmt" + "sync" + "testing" + + "github.com/prometheus/alertmanager/alertobserver" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/types" + "github.com/stretchr/testify/assert" +) + +func TestAlertLifeCycleObserverLimiter(t *testing.T) { + tenant := "fake" + lim := limits{ + tenant: tenant, + limit: 2, + } + limiter := NewAlertLifeCycleObserverLimiter(tenant, lim) + assert.Equal(t, 2, limiter.Level()) +} + +type limits struct { + tenant string + limit int +} + +func (l limits) AlertmanagerAlertLifeCycleObserverLevel(tenant string) int { + if tenant == l.tenant { + return l.limit + } + return 0 +} + +func TestLogAlertLifeCycleObserver(t *testing.T) { + logger := &FakeLogger{} + alerts := []*types.Alert{{}, {}} + ctx := context.Background() + ctx = notify.WithReceiverName(ctx, "rcv") + ctx = notify.WithGroupKey(ctx, "key") + + for _, tc := range []struct { + event string + logLvl []int + alerts []*types.Alert + meta alertobserver.AlertEventMeta + expectedMsg string + expectedLogCount int + expectedLoggedKeys []string + expectedMissingKeys []string + }{ + { + event: alertobserver.EventAlertAddedToAggrGroup, + alerts: alerts, + meta: alertobserver.AlertEventMeta{}, + logLvl: []int{0}, + expectedLogCount: 0, + }, + { + event: alertobserver.EventAlertReceived, + alerts: alerts, + meta: alertobserver.AlertEventMeta{}, + logLvl: []int{1, 2, 3, 4}, + expectedLogCount: 0, + }, + { + event: alertobserver.EventAlertReceived, + alerts: alerts, + meta: alertobserver.AlertEventMeta{}, + logLvl: []int{5}, + expectedLogCount: 2, + expectedMsg: "Received", + expectedLoggedKeys: []string{"labels", "fingerprint"}, + }, + { + event: alertobserver.EventAlertRejected, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"msg": "test"}, + logLvl: []int{1, 2, 3, 4, 5}, + expectedLogCount: 2, + expectedMsg: "Rejected", + expectedLoggedKeys: []string{"reason", "labels", "fingerprint"}, + }, + { + event: alertobserver.EventAlertAddedToAggrGroup, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"groupKey": "test"}, + logLvl: []int{1, 2, 3, 4, 5}, + expectedLogCount: 2, + expectedMsg: "Added to aggregation group", + expectedLoggedKeys: []string{"groupKey", "labels", "fingerprint"}, + }, + { + event: alertobserver.EventAlertFailedAddToAggrGroup, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"groupKey": "test"}, + logLvl: []int{1, 2, 3, 4, 5}, + expectedLogCount: 2, + expectedMsg: "Failed to add aggregation group", + expectedLoggedKeys: []string{"reason", "labels", "fingerprint"}, + }, + { + event: alertobserver.EventAlertPipelineStart, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx}, + logLvl: []int{1}, + expectedLogCount: 0, + }, + { + event: alertobserver.EventAlertPipelineStart, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx}, + logLvl: []int{2, 3, 4}, + expectedLogCount: 1, + expectedMsg: "Entered the pipeline", + expectedLoggedKeys: []string{"groupKey", "receiver"}, + expectedMissingKeys: []string{"fingerprint"}, + }, + { + event: alertobserver.EventAlertPipelineStart, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx}, + logLvl: []int{5}, + expectedLogCount: 2, + expectedMsg: "Entered the pipeline", + expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprint"}, + expectedMissingKeys: []string{"labels"}, + }, + { + event: alertobserver.EventAlertPipelinePassStage, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx, "stageName": "FanoutStage"}, + logLvl: []int{1000}, + expectedLogCount: 0, + }, + { + event: alertobserver.EventAlertPipelinePassStage, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx, "stageName": "Notify"}, + logLvl: []int{1, 2}, + expectedLogCount: 0, + }, + { + event: alertobserver.EventAlertPipelinePassStage, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx, "stageName": "Notify"}, + logLvl: []int{3, 4, 5}, + expectedLogCount: 1, + expectedMsg: "Passed stage", + expectedLoggedKeys: []string{"groupKey", "receiver", "stage"}, + expectedMissingKeys: []string{"fingerprint"}, + }, + { + event: alertobserver.EventAlertSent, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx, "integration": "sns"}, + logLvl: []int{1, 2, 3}, + expectedLogCount: 1, + expectedMsg: "Sent", + expectedLoggedKeys: []string{"groupKey", "receiver"}, + expectedMissingKeys: []string{"fingerprint"}, + }, + { + event: alertobserver.EventAlertSent, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx, "integration": "sns"}, + logLvl: []int{4, 5}, + expectedLogCount: 2, + expectedMsg: "Sent", + expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprint"}, + expectedMissingKeys: []string{"labels"}, + }, + { + event: alertobserver.EventAlertSendFailed, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx, "integration": "sns"}, + logLvl: []int{1, 2, 3}, + expectedLogCount: 1, + expectedMsg: "Send failed", + expectedLoggedKeys: []string{"groupKey", "receiver"}, + expectedMissingKeys: []string{"fingerprint"}, + }, + { + event: alertobserver.EventAlertSendFailed, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx, "integration": "sns"}, + logLvl: []int{4, 5}, + expectedLogCount: 2, + expectedMsg: "Send failed", + expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprint"}, + expectedMissingKeys: []string{"labels"}, + }, + { + event: alertobserver.EventAlertMuted, + alerts: alerts, + meta: alertobserver.AlertEventMeta{"ctx": ctx}, + logLvl: []int{1, 2, 3, 4, 5}, + expectedLogCount: 2, + expectedMsg: "Muted", + expectedLoggedKeys: []string{"groupKey", "fingerprint"}, + expectedMissingKeys: []string{"labels"}, + }, + } { + tc := tc + for _, logLvl := range tc.logLvl { + logger.clear() + l := NewAlertLifeCycleObserverLimiter("fake", limits{tenant: "fake", limit: logLvl}) + o := NewLogAlertLifeCycleObserver(logger, "fake", l) + o.Observe(tc.event, tc.alerts, tc.meta) + assert.Equal(t, tc.expectedLogCount, len(logger.loggedValues)) + for i := 0; i < tc.expectedLogCount; i++ { + loggedValues := logger.loggedValues[i] + assert.Equal(t, tc.expectedMsg, loggedValues["msg"]) + for _, v := range tc.expectedLoggedKeys { + _, ok := loggedValues[v] + assert.True(t, ok, fmt.Sprintf("'%v' is missing from the log", v)) + } + for _, v := range tc.expectedMissingKeys { + _, ok := loggedValues[v] + assert.False(t, ok, fmt.Sprintf("'%v' should be excluded from the log", v)) + } + } + } + } +} + +type FakeLogger struct { + loggedValues []map[string]string + mtx sync.RWMutex +} + +func (l *FakeLogger) Log(keyvals ...interface{}) error { + l.mtx.Lock() + defer l.mtx.Unlock() + params := make(map[string]string) + for i, v := range keyvals { + if i%2 == 0 { + params[fmt.Sprintf("%v", v)] = fmt.Sprintf("%v", keyvals[i+1]) + } + } + l.loggedValues = append(l.loggedValues, params) + return nil +} + +func (l *FakeLogger) clear() { + l.loggedValues = l.loggedValues[:0] +} diff --git a/pkg/alertmanager/alertmanager.go b/pkg/alertmanager/alertmanager.go index d67f26eefd..d92d9b8fd5 100644 --- a/pkg/alertmanager/alertmanager.go +++ b/pkg/alertmanager/alertmanager.go @@ -16,11 +16,13 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/pkg/errors" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/api" "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/cluster/clusterpb" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/dispatch" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/alertmanager/inhibit" "github.com/prometheus/alertmanager/nflog" "github.com/prometheus/alertmanager/notify" @@ -120,6 +122,8 @@ type Alertmanager struct { configHashMetric prometheus.Gauge rateLimitedNotifications *prometheus.CounterVec + + alertLCObserver alertobserver.LifeCycleObserver } var ( @@ -155,7 +159,7 @@ type Replicator interface { } // New creates a new Alertmanager. -func New(cfg *Config, reg *prometheus.Registry) (*Alertmanager, error) { +func New(cfg *Config, reg *prometheus.Registry, o alertobserver.LifeCycleObserver) (*Alertmanager, error) { if cfg.TenantDataDir == "" { return nil, fmt.Errorf("directory for tenant-specific AlertManager is not configured") } @@ -174,6 +178,7 @@ func New(cfg *Config, reg *prometheus.Registry) (*Alertmanager, error) { Help: "Number of rate-limited notifications per integration.", }, []string{"integration"}), // "integration" is consistent with other alertmanager metrics. + alertLCObserver: o, } am.registry = reg @@ -243,7 +248,7 @@ func New(cfg *Config, reg *prometheus.Registry) (*Alertmanager, error) { } } - am.pipelineBuilder = notify.NewPipelineBuilder(am.registry) + am.pipelineBuilder = notify.NewPipelineBuilder(am.registry, featurecontrol.NoopFlags{}) am.wg.Add(1) go func() { @@ -253,7 +258,7 @@ func New(cfg *Config, reg *prometheus.Registry) (*Alertmanager, error) { var callback mem.AlertStoreCallback if am.cfg.Limits != nil { - callback = newAlertsLimiter(am.cfg.UserID, am.cfg.Limits, reg) + callback = newAlertsLimiter(am.cfg.UserID, am.cfg.Limits, reg, o) } am.alerts, err = mem.NewAlerts(context.Background(), am.marker, am.cfg.GCInterval, callback, am.logger, am.registry) if err != nil { @@ -271,7 +276,8 @@ func New(cfg *Config, reg *prometheus.Registry) (*Alertmanager, error) { GroupFunc: func(f1 func(*dispatch.Route) bool, f2 func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) { return am.dispatcher.Groups(f1, f2) }, - Concurrency: am.cfg.APIConcurrency, + Concurrency: am.cfg.APIConcurrency, + AlertLCObserver: am.alertLCObserver, }) if err != nil { return nil, fmt.Errorf("failed to create api: %v", err) @@ -393,6 +399,7 @@ func (am *Alertmanager) ApplyConfig(userID string, conf *config.Config, rawCfg s timeIntervals, am.nflog, am.state, + am.alertLCObserver, ) am.lastPipeline = pipeline am.dispatcher = dispatch.NewDispatcher( @@ -404,6 +411,7 @@ func (am *Alertmanager) ApplyConfig(userID string, conf *config.Config, rawCfg s &dispatcherLimits{tenant: am.cfg.UserID, limits: am.cfg.Limits}, log.With(am.logger, "component", "dispatcher"), am.dispatcherMetrics, + am.alertLCObserver, ) go am.dispatcher.Run() @@ -495,7 +503,7 @@ func buildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, fire return } n = wrapper(name, n) - integrations = append(integrations, notify.NewIntegration(n, rs, name, i)) + integrations = append(integrations, notify.NewIntegration(n, rs, name, i, nc.Name)) } ) @@ -637,9 +645,11 @@ type alertsLimiter struct { sizes map[model.Fingerprint]int count int totalSize int + + alertLCObserver alertobserver.LifeCycleObserver } -func newAlertsLimiter(tenant string, limits Limits, reg prometheus.Registerer) *alertsLimiter { +func newAlertsLimiter(tenant string, limits Limits, reg prometheus.Registerer, o alertobserver.LifeCycleObserver) *alertsLimiter { limiter := &alertsLimiter{ tenant: tenant, limits: limits, @@ -648,6 +658,7 @@ func newAlertsLimiter(tenant string, limits Limits, reg prometheus.Registerer) * Name: "alertmanager_alerts_insert_limited_total", Help: "Number of failures to insert new alerts to in-memory alert store.", }), + alertLCObserver: o, } promauto.With(reg).NewGaugeFunc(prometheus.GaugeOpts{ @@ -686,6 +697,10 @@ func (a *alertsLimiter) PreStore(alert *types.Alert, existing bool) error { if !existing && countLimit > 0 && (a.count+1) > countLimit { a.failureCounter.Inc() + if a.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{"msg": "count limit"} + a.alertLCObserver.Observe(alertobserver.EventAlertRejected, []*types.Alert{alert}, m) + } return fmt.Errorf(errTooManyAlerts, countLimit, alert.Name()) } @@ -695,6 +710,10 @@ func (a *alertsLimiter) PreStore(alert *types.Alert, existing bool) error { if sizeLimit > 0 && (a.totalSize+sizeDiff) > sizeLimit { a.failureCounter.Inc() + if a.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{"msg": "size limit"} + a.alertLCObserver.Observe(alertobserver.EventAlertRejected, []*types.Alert{alert}, m) + } return fmt.Errorf(errAlertsTooBig, sizeLimit) } diff --git a/pkg/alertmanager/alertmanager_test.go b/pkg/alertmanager/alertmanager_test.go index 6859fb5086..362d48168b 100644 --- a/pkg/alertmanager/alertmanager_test.go +++ b/pkg/alertmanager/alertmanager_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/go-kit/log" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/types" "github.com/prometheus/client_golang/prometheus" @@ -39,31 +40,9 @@ func createAlertmanagerAndSendAlerts(t *testing.T, alertGroups, groupsLimit, exp user := "test" reg := prometheus.NewPedanticRegistry() - am, err := New(&Config{ - UserID: user, - Logger: log.NewNopLogger(), - Limits: &mockAlertManagerLimits{maxDispatcherAggregationGroups: groupsLimit}, - TenantDataDir: t.TempDir(), - ExternalURL: &url.URL{Path: "/am"}, - ShardingEnabled: false, - GCInterval: 30 * time.Minute, - }, reg) - require.NoError(t, err) + am := createAlertmanager(t, user, reg, groupsLimit, nil) defer am.StopAndWait() - cfgRaw := `receivers: -- name: 'prod' - -route: - group_by: ['alertname'] - group_wait: 10ms - group_interval: 10ms - receiver: 'prod'` - - cfg, err := config.Load(cfgRaw) - require.NoError(t, err) - require.NoError(t, am.ApplyConfig(user, cfg, cfgRaw)) - now := time.Now() for i := 0; i < alertGroups; i++ { @@ -113,6 +92,33 @@ route: }) } +func createAlertmanager(t *testing.T, user string, reg *prometheus.Registry, groupsLimit int, o alertobserver.LifeCycleObserver) *Alertmanager { + am, err := New(&Config{ + UserID: user, + Logger: log.NewNopLogger(), + Limits: &mockAlertManagerLimits{maxDispatcherAggregationGroups: groupsLimit}, + TenantDataDir: t.TempDir(), + ExternalURL: &url.URL{Path: "/am"}, + ShardingEnabled: false, + GCInterval: 30 * time.Minute, + }, reg, o) + require.NoError(t, err) + + cfgRaw := `receivers: +- name: 'prod' + +route: + group_by: ['alertname'] + group_wait: 10ms + group_interval: 10ms + receiver: 'prod'` + + cfg, err := config.Load(cfgRaw) + require.NoError(t, err) + require.NoError(t, am.ApplyConfig(user, cfg, cfgRaw)) + return am +} + var ( alert1 = model.Alert{ Labels: model.LabelSet{"alert": "first", "alertname": "alert1"}, @@ -152,7 +158,7 @@ func TestAlertsLimiterWithNoLimits(t *testing.T) { {alert: &types.Alert{Alert: alert1}, delete: true, expectedCount: 0, expectedTotalSize: 0}, } - testLimiter(t, &mockAlertManagerLimits{}, ops) + testLimiter(t, &mockAlertManagerLimits{}, ops, nil) } func TestAlertsLimiterWithCountLimit(t *testing.T) { @@ -171,7 +177,15 @@ func TestAlertsLimiterWithCountLimit(t *testing.T) { {alert: &types.Alert{Alert: alert2}, delete: true, expectedCount: 0, expectedTotalSize: 0}, } - testLimiter(t, &mockAlertManagerLimits{maxAlertsCount: 1}, ops) + testLimiter(t, &mockAlertManagerLimits{maxAlertsCount: 1}, ops, nil) + // make sure we log the event if the observer is set + logger := &FakeLogger{} + l := NewAlertLifeCycleObserverLimiter("fake", limits{tenant: "fake", limit: 1000}) + o := NewLogAlertLifeCycleObserver(logger, "fake", l) + testLimiter(t, &mockAlertManagerLimits{maxAlertsCount: 1}, ops, o) + assert.Equal(t, 1, len(logger.loggedValues)) + assert.Equal(t, "Rejected", logger.loggedValues[0]["msg"]) + assert.Equal(t, "count limit", logger.loggedValues[0]["reason"]) } func TestAlertsLimiterWithSizeLimit(t *testing.T) { @@ -191,7 +205,15 @@ func TestAlertsLimiterWithSizeLimit(t *testing.T) { // Prerequisite for this test. We set size limit to alert2Size, but inserting alert1 first will prevent insertion of alert2. require.True(t, alert2Size > alert1Size) - testLimiter(t, &mockAlertManagerLimits{maxAlertsSizeBytes: alert2Size}, ops) + testLimiter(t, &mockAlertManagerLimits{maxAlertsSizeBytes: alert2Size}, ops, nil) + // make sure we log the event if the observer is set + logger := &FakeLogger{} + l := NewAlertLifeCycleObserverLimiter("fake", limits{tenant: "fake", limit: 1000}) + o := NewLogAlertLifeCycleObserver(logger, "fake", l) + testLimiter(t, &mockAlertManagerLimits{maxAlertsSizeBytes: alert2Size}, ops, o) + assert.Equal(t, 2, len(logger.loggedValues)) + assert.Equal(t, "Rejected", logger.loggedValues[0]["msg"]) + assert.Equal(t, "size limit", logger.loggedValues[0]["reason"]) } func TestAlertsLimiterWithSizeLimitAndAnnotationUpdate(t *testing.T) { @@ -203,21 +225,21 @@ func TestAlertsLimiterWithSizeLimitAndAnnotationUpdate(t *testing.T) { testLimiter(t, &mockAlertManagerLimits{maxAlertsSizeBytes: alert2Size}, []callbackOp{ {alert: &types.Alert{Alert: alert2}, existing: false, expectedCount: 1, expectedTotalSize: alert2Size}, {alert: &types.Alert{Alert: alert2WithMoreAnnotations}, existing: true, expectedInsertError: fmt.Errorf(errAlertsTooBig, alert2Size), expectedCount: 1, expectedTotalSize: alert2Size}, - }) + }, nil) // Updating alert with larger annotations in the limit works fine. testLimiter(t, &mockAlertManagerLimits{maxAlertsSizeBytes: alert2WithMoreAnnotationsSize}, []callbackOp{ {alert: &types.Alert{Alert: alert2}, existing: false, expectedCount: 1, expectedTotalSize: alert2Size}, {alert: &types.Alert{Alert: alert2WithMoreAnnotations}, existing: true, expectedCount: 1, expectedTotalSize: alert2WithMoreAnnotationsSize}, {alert: &types.Alert{Alert: alert2}, existing: true, expectedCount: 1, expectedTotalSize: alert2Size}, - }) + }, nil) } // testLimiter sends sequence of alerts to limiter, and checks if limiter updated reacted correctly. -func testLimiter(t *testing.T, limits Limits, ops []callbackOp) { +func testLimiter(t *testing.T, limits Limits, ops []callbackOp, o alertobserver.LifeCycleObserver) { reg := prometheus.NewPedanticRegistry() - limiter := newAlertsLimiter("test", limits, reg) + limiter := newAlertsLimiter("test", limits, reg, o) for ix, op := range ops { if op.delete { @@ -236,3 +258,40 @@ func testLimiter(t *testing.T, limits Limits, ops []callbackOp) { assert.Equal(t, op.expectedTotalSize, totalSize, "wrong total size, op %d", ix) } } + +func TestRunAlertmanagerWithObserver(t *testing.T) { + logger := &FakeLogger{} + l := NewAlertLifeCycleObserverLimiter("fake", limits{tenant: "fake", limit: 1000}) + o := NewLogAlertLifeCycleObserver(logger, "fake", l) + + user := "test" + + reg := prometheus.NewPedanticRegistry() + am := createAlertmanager(t, user, reg, 100, o) + defer am.StopAndWait() + + now := time.Now() + + inputAlerts := []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "z": "y", + }, + Annotations: model.LabelSet{"foo": "bar"}, + StartsAt: now, + EndsAt: now.Add(5 * time.Minute), + GeneratorURL: "http://example.com/prometheus", + }, + UpdatedAt: now, + Timeout: false, + }, + } + require.NoError(t, am.alerts.Put(inputAlerts...)) + + test.Poll(t, 3*time.Second, true, func() interface{} { + logger.mtx.RLock() + defer logger.mtx.RUnlock() + return len(logger.loggedValues) > 10 + }) +} diff --git a/pkg/alertmanager/multitenant.go b/pkg/alertmanager/multitenant.go index 49a3e401ad..6976cc1a30 100644 --- a/pkg/alertmanager/multitenant.go +++ b/pkg/alertmanager/multitenant.go @@ -15,6 +15,7 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/pkg/errors" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/cluster/clusterpb" amconfig "github.com/prometheus/alertmanager/config" @@ -90,6 +91,8 @@ type MultitenantAlertmanagerConfig struct { EnabledTenants flagext.StringSliceCSV `yaml:"enabled_tenants"` DisabledTenants flagext.StringSliceCSV `yaml:"disabled_tenants"` + + AlertLifeCycleObserverFn func(config *Config) alertobserver.LifeCycleObserver `yaml:"-"` } type ClusterConfig struct { @@ -227,6 +230,9 @@ type Limits interface { // AlertmanagerMaxAlertsSizeBytes returns total max size of alerts that tenant can have active at the same time. 0 = no limit. // Size of the alert is computed from alert labels, annotations and generator URL. AlertmanagerMaxAlertsSizeBytes(tenant string) int + + // AlertmanagerAlertLifeCycleObserverLevel returns an int that controls how much logs the LifeCycleObserver will collect. 0 = no logs. + AlertmanagerAlertLifeCycleObserverLevel(tenant string) int } // A MultitenantAlertmanager manages Alertmanager instances for multiple @@ -961,7 +967,7 @@ func (am *MultitenantAlertmanager) newAlertmanager(userID string, amConfig *amco return nil, errors.Wrapf(err, "failed to create per-tenant directory %v", tenantDir) } - newAM, err := New(&Config{ + c := &Config{ UserID: userID, TenantDataDir: tenantDir, Logger: am.logger, @@ -977,7 +983,14 @@ func (am *MultitenantAlertmanager) newAlertmanager(userID string, amConfig *amco Limits: am.limits, APIConcurrency: am.cfg.APIConcurrency, GCInterval: am.cfg.GCInterval, - }, reg) + } + + var o alertobserver.LifeCycleObserver + if am.cfg.AlertLifeCycleObserverFn != nil { + o = am.cfg.AlertLifeCycleObserverFn(c) + } + + newAM, err := New(c, reg, o) if err != nil { return nil, fmt.Errorf("unable to start Alertmanager for user %v: %v", userID, err) } diff --git a/pkg/alertmanager/multitenant_test.go b/pkg/alertmanager/multitenant_test.go index 38057bb144..e20deb014d 100644 --- a/pkg/alertmanager/multitenant_test.go +++ b/pkg/alertmanager/multitenant_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/go-kit/log" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/cluster/clusterpb" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/pkg/labels" @@ -188,20 +189,45 @@ receivers: originalFiles, err := listFiles(storeDir) require.NoError(t, err) require.Equal(t, 3, len(originalFiles)) - - cfg := mockAlertmanagerConfig(t) - cfg.DataDir = storeDir - reg := prometheus.NewPedanticRegistry() - am, err := createMultitenantAlertmanager(cfg, nil, nil, store, nil, nil, log.NewNopLogger(), reg) - require.NoError(t, err) - for i := 0; i < 5; i++ { - err = am.loadAndSyncConfigs(context.Background(), reasonPeriodic) - require.NoError(t, err) - require.Len(t, am.alertmanagers, 2) - files, err := listFiles(storeDir) - require.NoError(t, err) - // Verify if the files were not deleted - require.Equal(t, originalFiles, files) + for _, tc := range []struct { + name string + observerFn func(config *Config) alertobserver.LifeCycleObserver + }{ + { + name: "running with alert observer", + observerFn: func(config *Config) alertobserver.LifeCycleObserver { + return &LogAlertLifeCycleObserver{} + }, + }, + { + name: "running without alert observer", + }, + } { + t.Run(tc.name, func(t *testing.T) { + cfg := mockAlertmanagerConfig(t) + cfg.AlertLifeCycleObserverFn = tc.observerFn + cfg.DataDir = storeDir + reg := prometheus.NewPedanticRegistry() + am, err := createMultitenantAlertmanager(cfg, nil, nil, store, nil, nil, log.NewNopLogger(), reg) + require.NoError(t, err) + for i := 0; i < 5; i++ { + err = am.loadAndSyncConfigs(context.Background(), reasonPeriodic) + require.NoError(t, err) + require.Len(t, am.alertmanagers, 2) + files, err := listFiles(storeDir) + require.NoError(t, err) + // Verify if the files were not deleted + require.Equal(t, originalFiles, files) + // Verify that alertLCObserver is set when the provider function is present + for _, v := range am.alertmanagers { + if tc.observerFn == nil { + require.Nil(t, v.alertLCObserver) + } else { + require.NotNil(t, v.alertLCObserver) + } + } + } + }) } } @@ -2217,6 +2243,7 @@ type mockAlertManagerLimits struct { maxDispatcherAggregationGroups int maxAlertsCount int maxAlertsSizeBytes int + alertLifeCycleObserverLevel int } func (m *mockAlertManagerLimits) AlertmanagerMaxConfigSize(tenant string) int { @@ -2258,3 +2285,7 @@ func (m *mockAlertManagerLimits) AlertmanagerMaxAlertsCount(_ string) int { func (m *mockAlertManagerLimits) AlertmanagerMaxAlertsSizeBytes(_ string) int { return m.maxAlertsSizeBytes } + +func (m *mockAlertManagerLimits) AlertmanagerAlertLifeCycleObserverLevel(_ string) int { + return m.alertLifeCycleObserverLevel +} diff --git a/pkg/cortex/modules.go b/pkg/cortex/modules.go index 64cce59870..6693dd7a49 100644 --- a/pkg/cortex/modules.go +++ b/pkg/cortex/modules.go @@ -11,6 +11,7 @@ import ( "github.com/opentracing-contrib/go-stdlib/nethttp" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/rules" @@ -638,6 +639,15 @@ func (t *Cortex) initConfig() (serv services.Service, err error) { func (t *Cortex) initAlertManager() (serv services.Service, err error) { t.Cfg.Alertmanager.ShardingRing.ListenPort = t.Cfg.Server.GRPCListenPort + if t.Cfg.Alertmanager.AlertLifeCycleObserverFn == nil { + t.Cfg.Alertmanager.AlertLifeCycleObserverFn = func(config *alertmanager.Config) alertobserver.LifeCycleObserver { + if config.Limits == nil { + return nil + } + observerLimiter := alertmanager.NewAlertLifeCycleObserverLimiter(config.UserID, config.Limits) + return alertmanager.NewLogAlertLifeCycleObserver(config.Logger, config.UserID, observerLimiter) + } + } // Initialise the store. store, err := alertstore.NewAlertStore(context.Background(), t.Cfg.AlertmanagerStorage, t.Overrides, util_log.Logger, prometheus.DefaultRegisterer) diff --git a/pkg/util/validation/limits.go b/pkg/util/validation/limits.go index d60b2ca8e6..033cb4bfcc 100644 --- a/pkg/util/validation/limits.go +++ b/pkg/util/validation/limits.go @@ -136,6 +136,7 @@ type Limits struct { AlertmanagerMaxDispatcherAggregationGroups int `yaml:"alertmanager_max_dispatcher_aggregation_groups" json:"alertmanager_max_dispatcher_aggregation_groups"` AlertmanagerMaxAlertsCount int `yaml:"alertmanager_max_alerts_count" json:"alertmanager_max_alerts_count"` AlertmanagerMaxAlertsSizeBytes int `yaml:"alertmanager_max_alerts_size_bytes" json:"alertmanager_max_alerts_size_bytes"` + AlertmanagerAlertLifeCycleObserverLevel int `yaml:"alertmanager_alert_lifecycle_observer_level" json:"alertmanager_alert_lifecycle_observer_level"` DisabledRuleGroups DisabledRuleGroups `yaml:"disabled_rule_groups" json:"disabled_rule_groups" doc:"nocli|description=list of rule groups to disable"` } @@ -217,6 +218,7 @@ func (l *Limits) RegisterFlags(f *flag.FlagSet) { f.IntVar(&l.AlertmanagerMaxDispatcherAggregationGroups, "alertmanager.max-dispatcher-aggregation-groups", 0, "Maximum number of aggregation groups in Alertmanager's dispatcher that a tenant can have. Each active aggregation group uses single goroutine. When the limit is reached, dispatcher will not dispatch alerts that belong to additional aggregation groups, but existing groups will keep working properly. 0 = no limit.") f.IntVar(&l.AlertmanagerMaxAlertsCount, "alertmanager.max-alerts-count", 0, "Maximum number of alerts that a single user can have. Inserting more alerts will fail with a log message and metric increment. 0 = no limit.") f.IntVar(&l.AlertmanagerMaxAlertsSizeBytes, "alertmanager.max-alerts-size-bytes", 0, "Maximum total size of alerts that a single user can have, alert size is the sum of the bytes of its labels, annotations and generatorURL. Inserting more alerts will fail with a log message and metric increment. 0 = no limit.") + f.IntVar(&l.AlertmanagerAlertLifeCycleObserverLevel, "alertmanager.alert-lifecycle-observer-level", 0, "Level of logs the LifeCycleObserver will collect. 0 = no logs.") } // Validate the limits config and returns an error if the validation @@ -676,6 +678,10 @@ func (o *Overrides) AlertmanagerMaxAlertsSizeBytes(userID string) int { return o.GetOverridesForUser(userID).AlertmanagerMaxAlertsSizeBytes } +func (o *Overrides) AlertmanagerAlertLifeCycleObserverLevel(userID string) int { + return o.GetOverridesForUser(userID).AlertmanagerAlertLifeCycleObserverLevel +} + func (o *Overrides) DisabledRuleGroups(userID string) DisabledRuleGroups { if o.tenantLimits != nil { l := o.tenantLimits.ByUserID(userID) From ca9ebc4a6fb6c0f1532a5a6a572a63ee630cb929 Mon Sep 17 00:00:00 2001 From: Emmanuel Lodovice Date: Thu, 30 Nov 2023 00:59:30 -0800 Subject: [PATCH 3/3] Address comments Signed-off-by: Emmanuel Lodovice --- pkg/alertmanager/alert_observer.go | 111 ++++++++++------- pkg/alertmanager/alert_observer_test.go | 155 ++++++++++++++++++++++-- 2 files changed, 206 insertions(+), 60 deletions(-) diff --git a/pkg/alertmanager/alert_observer.go b/pkg/alertmanager/alert_observer.go index b7cb7affcc..9f0c2b8289 100644 --- a/pkg/alertmanager/alert_observer.go +++ b/pkg/alertmanager/alert_observer.go @@ -2,6 +2,7 @@ package alertmanager import ( "context" + "strings" "github.com/go-kit/log" "github.com/go-kit/log/level" @@ -46,9 +47,33 @@ func NewLogAlertLifeCycleObserver(logger log.Logger, user string, limiter *Alert // Observe implements LifeCycleObserver func (o *LogAlertLifeCycleObserver) Observe(event string, alerts []*types.Alert, meta alertobserver.AlertEventMeta) { - if alerts == nil || o.limiter == nil || o.limiter.Level() <= 0 { + if alerts == nil || o.limiter == nil || o.limiter.Level() <= 0 || !o.shouldLog(event) { return } + + switch event { + case alertobserver.EventAlertReceived: + o.Received(alerts) + case alertobserver.EventAlertRejected: + o.Rejected(alerts, meta) + case alertobserver.EventAlertAddedToAggrGroup: + o.AddedAggrGroup(alerts, meta) + case alertobserver.EventAlertFailedAddToAggrGroup: + o.FailedAddToAggrGroup(alerts, meta) + case alertobserver.EventAlertPipelineStart: + o.PipelineStart(alerts, meta) + case alertobserver.EventAlertPipelinePassStage: + o.PipelinePassStage(alerts, meta) + case alertobserver.EventAlertSent: + o.Sent(alerts, meta) + case alertobserver.EventAlertSendFailed: + o.SendFailed(alerts, meta) + case alertobserver.EventAlertMuted: + o.Muted(alerts, meta) + } +} + +func (o *LogAlertLifeCycleObserver) shouldLog(event string) bool { // The general idea of having levels in the limiter is to control the volume of logs AM is producing. The observer // logs many types of events and some events are more important than others. By configuring the level we can // continue to have observability on the alerts at lower granularity if we see that the volume of logs is getting @@ -87,35 +112,30 @@ func (o *LogAlertLifeCycleObserver) Observe(event string, alerts []*types.Alert, // * Alert in aggregation group pipeline start // * Aggregation Group passed a stage in the pipeline // * Alert in aggregation group is Sent / Failed - + if o.limiter == nil || o.limiter.Level() <= 0 { + return false + } + logLvl := o.limiter.Level() switch event { case alertobserver.EventAlertReceived: - o.Received(alerts) - case alertobserver.EventAlertRejected: - o.Rejected(alerts, meta) - case alertobserver.EventAlertAddedToAggrGroup: - o.AddedAggrGroup(alerts, meta) - case alertobserver.EventAlertFailedAddToAggrGroup: - o.FailedAddToAggrGroup(alerts, meta) + if logLvl < 5 { + return false + } case alertobserver.EventAlertPipelineStart: - o.PipelineStart(alerts, meta) + if logLvl < 2 { + return false + } case alertobserver.EventAlertPipelinePassStage: - o.PipelinePassStage(alerts, meta) - case alertobserver.EventAlertSent: - o.Sent(alerts, meta) - case alertobserver.EventAlertSendFailed: - o.SendFailed(alerts, meta) - case alertobserver.EventAlertMuted: - o.Muted(alerts, meta) + if logLvl < 3 { + return false + } } + return true } func (o *LogAlertLifeCycleObserver) Received(alerts []*types.Alert) { - if o.limiter.Level() < 5 { - return - } for _, a := range alerts { - o.logWithAlert(a, true, "msg", "Received") + o.logWithAlertWithLabels(a, "msg", "Received") } } @@ -125,7 +145,7 @@ func (o *LogAlertLifeCycleObserver) Rejected(alerts []*types.Alert, meta alertob reason = "Unknown" } for _, a := range alerts { - o.logWithAlert(a, true, "msg", "Rejected", "reason", reason) + o.logWithAlertWithLabels(a, "msg", "Rejected", "reason", reason) } } @@ -135,7 +155,7 @@ func (o *LogAlertLifeCycleObserver) AddedAggrGroup(alerts []*types.Alert, meta a return } for _, a := range alerts { - o.logWithAlert(a, true, "msg", "Added to aggregation group", "groupKey", groupKey) + o.logWithAlertWithLabels(a, "msg", "Added to aggregation group", "groupKey", groupKey) } } @@ -145,15 +165,11 @@ func (o *LogAlertLifeCycleObserver) FailedAddToAggrGroup(alerts []*types.Alert, reason = "Unknown" } for _, a := range alerts { - o.logWithAlert(a, true, "msg", "Failed to add aggregation group", "reason", reason) + o.logWithAlertWithLabels(a, "msg", "Failed to add aggregation group", "reason", reason) } } func (o *LogAlertLifeCycleObserver) PipelineStart(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { - logLvl := o.limiter.Level() - if logLvl < 2 { - return - } ctx, ok := meta["ctx"] if !ok { return @@ -166,19 +182,14 @@ func (o *LogAlertLifeCycleObserver) PipelineStart(alerts []*types.Alert, meta al if !ok { return } - if logLvl < 5 { + if o.limiter.Level() < 5 { level.Info(o.logger).Log("msg", "Entered the pipeline", "groupKey", groupKey, "receiver", receiver, "alertsCount", len(alerts)) } else { - for _, a := range alerts { - o.logWithAlert(a, false, "msg", "Entered the pipeline", "groupKey", groupKey, "receiver", receiver) - } + o.logWithAlertFingerprints(alerts, "msg", "Entered the pipeline", "groupKey", groupKey, "receiver", receiver) } } func (o *LogAlertLifeCycleObserver) PipelinePassStage(alerts []*types.Alert, meta alertobserver.AlertEventMeta) { - if o.limiter.Level() < 3 { - return - } stageName, ok := meta["stageName"] if !ok { return @@ -223,9 +234,7 @@ func (o *LogAlertLifeCycleObserver) Sent(alerts []*types.Alert, meta alertobserv if o.limiter.Level() < 4 { level.Info(o.logger).Log("msg", "Sent", "groupKey", groupKey, "receiver", receiver, "integration", integration, "alertsCount", len(alerts)) } else { - for _, a := range alerts { - o.logWithAlert(a, false, "msg", "Sent", "groupKey", groupKey, "receiver", receiver, "integration", integration) - } + o.logWithAlertFingerprints(alerts, "msg", "Sent", "groupKey", groupKey, "receiver", receiver, "integration", integration) } } @@ -249,9 +258,7 @@ func (o *LogAlertLifeCycleObserver) SendFailed(alerts []*types.Alert, meta alert if o.limiter.Level() < 4 { level.Info(o.logger).Log("msg", "Send failed", "groupKey", groupKey, "receiver", receiver, "integration", integration, "alertsCount", len(alerts)) } else { - for _, a := range alerts { - o.logWithAlert(a, false, "msg", "Send failed", "groupKey", groupKey, "receiver", receiver, "integration", integration) - } + o.logWithAlertFingerprints(alerts, "msg", "Send failed", "groupKey", groupKey, "receiver", receiver, "integration", integration) } } @@ -264,12 +271,10 @@ func (o *LogAlertLifeCycleObserver) Muted(alerts []*types.Alert, meta alertobser if !ok { return } - for _, a := range alerts { - o.logWithAlert(a, false, "msg", "Muted", "groupKey", groupKey) - } + o.logWithAlertFingerprints(alerts, "msg", "Muted", "groupKey", groupKey) } -func (o *LogAlertLifeCycleObserver) logWithAlert(alert *types.Alert, addLabels bool, keyvals ...interface{}) { +func (o *LogAlertLifeCycleObserver) logWithAlertWithLabels(alert *types.Alert, keyvals ...interface{}) { keyvals = append( keyvals, "fingerprint", @@ -278,9 +283,21 @@ func (o *LogAlertLifeCycleObserver) logWithAlert(alert *types.Alert, addLabels b alert.StartsAt.Unix(), "end", alert.EndsAt.Unix(), + "labels", + alert.Labels.String(), ) - if addLabels { - keyvals = append(keyvals, "labels", alert.Labels.String()) + level.Info(o.logger).Log(keyvals...) +} + +func (o *LogAlertLifeCycleObserver) logWithAlertFingerprints(alerts []*types.Alert, keyvals ...interface{}) { + fps := make([]string, len(alerts)) + for i, a := range alerts { + fps[i] = a.Fingerprint().String() } + keyvals = append( + keyvals, + "fingerprints", + strings.Join(fps, ","), + ) level.Info(o.logger).Log(keyvals...) } diff --git a/pkg/alertmanager/alert_observer_test.go b/pkg/alertmanager/alert_observer_test.go index 81cd23b532..0640f69352 100644 --- a/pkg/alertmanager/alert_observer_test.go +++ b/pkg/alertmanager/alert_observer_test.go @@ -5,10 +5,12 @@ import ( "fmt" "sync" "testing" + "time" "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" "github.com/stretchr/testify/assert" ) @@ -35,8 +37,26 @@ func (l limits) AlertmanagerAlertLifeCycleObserverLevel(tenant string) int { } func TestLogAlertLifeCycleObserver(t *testing.T) { + alert1 = model.Alert{ + Labels: model.LabelSet{"alert": "first", "alertname": "alert1"}, + Annotations: model.LabelSet{"job": "test"}, + StartsAt: time.Now(), + EndsAt: time.Now(), + GeneratorURL: "some URL", + } + + alert2 = model.Alert{ + Labels: model.LabelSet{"alert": "second", "alertname": "alert2"}, + Annotations: model.LabelSet{"job": "test", "cluster": "prod"}, + StartsAt: time.Now(), + EndsAt: time.Now(), + GeneratorURL: "some URL", + } logger := &FakeLogger{} - alerts := []*types.Alert{{}, {}} + alerts := []*types.Alert{ + {alert1, time.Now(), false}, + {alert2, time.Now(), false}, + } ctx := context.Background() ctx = notify.WithReceiverName(ctx, "rcv") ctx = notify.WithGroupKey(ctx, "key") @@ -50,6 +70,7 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { expectedLogCount int expectedLoggedKeys []string expectedMissingKeys []string + expectedKeyValues []map[string]string }{ { event: alertobserver.EventAlertAddedToAggrGroup, @@ -73,6 +94,16 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { expectedLogCount: 2, expectedMsg: "Received", expectedLoggedKeys: []string{"labels", "fingerprint"}, + expectedKeyValues: []map[string]string{ + { + "fingerprint": alert1.Fingerprint().String(), + "labels": alert1.Labels.String(), + }, + { + "fingerprint": alert2.Fingerprint().String(), + "labels": alert2.Labels.String(), + }, + }, }, { event: alertobserver.EventAlertRejected, @@ -82,6 +113,18 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { expectedLogCount: 2, expectedMsg: "Rejected", expectedLoggedKeys: []string{"reason", "labels", "fingerprint"}, + expectedKeyValues: []map[string]string{ + { + "fingerprint": alert1.Fingerprint().String(), + "labels": alert1.Labels.String(), + "reason": "test", + }, + { + "fingerprint": alert2.Fingerprint().String(), + "labels": alert2.Labels.String(), + "reason": "test", + }, + }, }, { event: alertobserver.EventAlertAddedToAggrGroup, @@ -91,15 +134,39 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { expectedLogCount: 2, expectedMsg: "Added to aggregation group", expectedLoggedKeys: []string{"groupKey", "labels", "fingerprint"}, + expectedKeyValues: []map[string]string{ + { + "fingerprint": alert1.Fingerprint().String(), + "labels": alert1.Labels.String(), + "groupKey": "test", + }, + { + "fingerprint": alert2.Fingerprint().String(), + "labels": alert2.Labels.String(), + "groupKey": "test", + }, + }, }, { event: alertobserver.EventAlertFailedAddToAggrGroup, alerts: alerts, - meta: alertobserver.AlertEventMeta{"groupKey": "test"}, + meta: alertobserver.AlertEventMeta{"msg": "test"}, logLvl: []int{1, 2, 3, 4, 5}, expectedLogCount: 2, expectedMsg: "Failed to add aggregation group", expectedLoggedKeys: []string{"reason", "labels", "fingerprint"}, + expectedKeyValues: []map[string]string{ + { + "fingerprint": alert1.Fingerprint().String(), + "labels": alert1.Labels.String(), + "reason": "test", + }, + { + "fingerprint": alert2.Fingerprint().String(), + "labels": alert2.Labels.String(), + "reason": "test", + }, + }, }, { event: alertobserver.EventAlertPipelineStart, @@ -116,17 +183,30 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { expectedLogCount: 1, expectedMsg: "Entered the pipeline", expectedLoggedKeys: []string{"groupKey", "receiver"}, - expectedMissingKeys: []string{"fingerprint"}, + expectedMissingKeys: []string{"fingerprint", "fingerprints"}, + expectedKeyValues: []map[string]string{ + { + "groupKey": "key", + "receiver": "rcv", + }, + }, }, { event: alertobserver.EventAlertPipelineStart, alerts: alerts, meta: alertobserver.AlertEventMeta{"ctx": ctx}, logLvl: []int{5}, - expectedLogCount: 2, + expectedLogCount: 1, expectedMsg: "Entered the pipeline", - expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprint"}, + expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprints"}, expectedMissingKeys: []string{"labels"}, + expectedKeyValues: []map[string]string{ + { + "fingerprints": fmt.Sprintf("%v,%v", alert1.Fingerprint().String(), alert2.Fingerprint().String()), + "groupKey": "key", + "receiver": "rcv", + }, + }, }, { event: alertobserver.EventAlertPipelinePassStage, @@ -151,6 +231,13 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { expectedMsg: "Passed stage", expectedLoggedKeys: []string{"groupKey", "receiver", "stage"}, expectedMissingKeys: []string{"fingerprint"}, + expectedKeyValues: []map[string]string{ + { + "stage": "Notify", + "groupKey": "key", + "receiver": "rcv", + }, + }, }, { event: alertobserver.EventAlertSent, @@ -160,17 +247,32 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { expectedLogCount: 1, expectedMsg: "Sent", expectedLoggedKeys: []string{"groupKey", "receiver"}, - expectedMissingKeys: []string{"fingerprint"}, + expectedMissingKeys: []string{"fingerprint", "fingerprints"}, + expectedKeyValues: []map[string]string{ + { + "groupKey": "key", + "receiver": "rcv", + "integration": "sns", + }, + }, }, { event: alertobserver.EventAlertSent, alerts: alerts, meta: alertobserver.AlertEventMeta{"ctx": ctx, "integration": "sns"}, logLvl: []int{4, 5}, - expectedLogCount: 2, + expectedLogCount: 1, expectedMsg: "Sent", - expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprint"}, + expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprints"}, expectedMissingKeys: []string{"labels"}, + expectedKeyValues: []map[string]string{ + { + "fingerprints": fmt.Sprintf("%v,%v", alert1.Fingerprint().String(), alert2.Fingerprint().String()), + "groupKey": "key", + "receiver": "rcv", + "integration": "sns", + }, + }, }, { event: alertobserver.EventAlertSendFailed, @@ -180,27 +282,47 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { expectedLogCount: 1, expectedMsg: "Send failed", expectedLoggedKeys: []string{"groupKey", "receiver"}, - expectedMissingKeys: []string{"fingerprint"}, + expectedMissingKeys: []string{"fingerprints", "fingerprint"}, + expectedKeyValues: []map[string]string{ + { + "integration": "sns", + "groupKey": "key", + "receiver": "rcv", + }, + }, }, { event: alertobserver.EventAlertSendFailed, alerts: alerts, meta: alertobserver.AlertEventMeta{"ctx": ctx, "integration": "sns"}, logLvl: []int{4, 5}, - expectedLogCount: 2, + expectedLogCount: 1, expectedMsg: "Send failed", - expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprint"}, + expectedLoggedKeys: []string{"groupKey", "receiver", "fingerprints"}, expectedMissingKeys: []string{"labels"}, + expectedKeyValues: []map[string]string{ + { + "fingerprints": fmt.Sprintf("%v,%v", alert1.Fingerprint().String(), alert2.Fingerprint().String()), + "groupKey": "key", + "receiver": "rcv", + }, + }, }, { event: alertobserver.EventAlertMuted, alerts: alerts, meta: alertobserver.AlertEventMeta{"ctx": ctx}, logLvl: []int{1, 2, 3, 4, 5}, - expectedLogCount: 2, + expectedLogCount: 1, expectedMsg: "Muted", - expectedLoggedKeys: []string{"groupKey", "fingerprint"}, + expectedLoggedKeys: []string{"groupKey", "fingerprints"}, expectedMissingKeys: []string{"labels"}, + expectedKeyValues: []map[string]string{ + { + "fingerprints": fmt.Sprintf("%v,%v", alert1.Fingerprint().String(), alert2.Fingerprint().String()), + "groupKey": "key", + }, + }, }, } { tc := tc @@ -221,6 +343,13 @@ func TestLogAlertLifeCycleObserver(t *testing.T) { _, ok := loggedValues[v] assert.False(t, ok, fmt.Sprintf("'%v' should be excluded from the log", v)) } + if tc.expectedKeyValues != nil { + for k, v := range tc.expectedKeyValues[i] { + loggedVal, ok := loggedValues[k] + assert.True(t, ok, fmt.Sprintf("'%v' is missing from the log", k)) + assert.Equal(t, v, loggedVal) + } + } } } }