pax_global_header 0000666 0000000 0000000 00000000064 14754146416 0014526 g ustar 00root root 0000000 0000000 52 comment=7a136d3c6e34b4f60a60e92d2f04c18c5fc47708
alertmanager-irc-relay-0.5.1/ 0000775 0000000 0000000 00000000000 14754146416 0016060 5 ustar 00root root 0000000 0000000 alertmanager-irc-relay-0.5.1/.github/ 0000775 0000000 0000000 00000000000 14754146416 0017420 5 ustar 00root root 0000000 0000000 alertmanager-irc-relay-0.5.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14754146416 0021455 5 ustar 00root root 0000000 0000000 alertmanager-irc-relay-0.5.1/.github/workflows/test.yml 0000664 0000000 0000000 00000000645 14754146416 0023164 0 ustar 00root root 0000000 0000000 on: [push, pull_request]
name: Test
jobs:
test:
strategy:
matrix:
go-version: [1.15.x, 1.18.x, 1.19.x]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test -v ./...
alertmanager-irc-relay-0.5.1/.gitignore 0000664 0000000 0000000 00000000037 14754146416 0020050 0 ustar 00root root 0000000 0000000 alertmanager-irc-relay
/vendor
alertmanager-irc-relay-0.5.1/CONTRIBUTING.md 0000664 0000000 0000000 00000002115 14754146416 0020310 0 ustar 00root root 0000000 0000000 # How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
## Community Guidelines
This project follows [Google's Open Source Community
Guidelines](https://opensource.google.com/conduct/).
alertmanager-irc-relay-0.5.1/Dockerfile 0000664 0000000 0000000 00000000227 14754146416 0020053 0 ustar 00root root 0000000 0000000 FROM golang:1.16
WORKDIR /go/src/app
COPY . .
RUN go get -d -v ./...
RUN go install -v ./...
CMD ["alertmanager-irc-relay", "--config=/config.yml"]
alertmanager-irc-relay-0.5.1/LICENSE 0000664 0000000 0000000 00000026136 14754146416 0017075 0 ustar 00root root 0000000 0000000
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.
alertmanager-irc-relay-0.5.1/README.md 0000664 0000000 0000000 00000007610 14754146416 0017343 0 ustar 00root root 0000000 0000000 # Alertmanager IRC Relay
Alertmanager IRC Relay is a bot relaying [Prometheus](https://prometheus.io/) alerts to IRC.
Alerts are received from Prometheus using
[Webhooks](https://prometheus.io/docs/alerting/configuration/#webhook-receiver-)
and are relayed to an IRC channel.
### Configuring and running the bot
To configure and run the bot you need to create a YAML configuration file and
pass it to the service. Running the service without a configuration will use
the default test values and connect to a default IRC channel, which you
probably do not want to do.
Example configuration:
```
# Start the HTTP server receiving alerts from Prometheus Webhook binding to
# this host/port.
#
http_host: localhost
http_port: 8000
# Connect to this IRC host/port.
#
# Note: SSL is enabled by default, use "irc_use_ssl: no" to disable.
# Set "irc_verify_ssl: no" to accept invalid SSL certificates (not recommended)
irc_host: irc.example.com
irc_port: 7000
# Optionally set the server password
irc_host_password: myserver_password
# Use this IRC nickname.
irc_nickname: myalertbot
# Password used to identify with NickServ
irc_nickname_password: mynickserv_key
# Use this IRC real name
irc_realname: myrealname
# Optionally pre-join certain channels.
#
# Note: If an alert is sent to a non # pre-joined channel the bot will join
# that channel anyway before sending the message. Of course this cannot work
# with password-protected channels.
irc_channels:
- name: "#mychannel"
- name: "#myprivatechannel"
password: myprivatechannel_key
# Define how IRC messages should be sent.
#
# Send only one message when webhook data is received.
# Note: By default a message is sent for each alert in the webhook data.
msg_once_per_alert_group: no
#
# Use PRIVMSG instead of NOTICE (default) to send messages.
# Note: Sending PRIVMSG from bots is bad practice, do not enable this unless
# necessary (e.g. unless NOTICEs would weaken your channel moderation policies)
use_privmsg: yes
# Define how IRC messages should be formatted.
#
# The formatting is based on golang's text/template .
msg_template: "Alert {{ .Labels.alertname }} on {{ .Labels.instance }} is {{ .Status }}"
# Note: When sending only one message per alert group the default
# msg_template is set to
# "Alert {{ .GroupLabels.alertname }} for {{ .GroupLabels.job }} is {{ .Status }}"
# Set the internal buffer size for alerts received but not yet sent to IRC.
alert_buffer_size: 2048
# Patterns used to guess whether NickServ is asking us to IDENTIFY
# Note: If you need to change this because the bot is not catching a request
# from a rather common NickServ, please consider sending a PR to update the
# default config instead.
nickserv_identify_patterns:
- "identify via /msg NickServ identify "
- "type /msg NickServ IDENTIFY password"
- "authenticate yourself to services with the IDENTIFY command"
# Rarely NickServ or ChanServ is reached at a specific hostname. Specify an
# override here
nickserv_name: NickServ
chanserv_name: ChanServ
```
Running the bot (assuming *$GOPATH* and *$PATH* are properly setup for go):
```
$ go install github.com/google/alertmanager-irc-relay
$ alertmanager-irc-relay --config /path/to/your/config/file
```
The configuration file can reference environment variables. It is then possible
to specify certain parameters directly when running the bot:
```
$ cat /path/to/your/config/file
...
http_port: $MY_SERVICE_PORT
...
irc_nickname_password: $NICKSERV_PASSWORD
...
$ export MY_SERVICE_PORT=8000 NICKSERV_PASSWORD=mynickserv_key
$ alertmanager-irc-relay --config /path/to/your/config/file
```
### Prometheus configuration
Prometheus can be configured following the official
[Webhooks](https://prometheus.io/docs/alerting/configuration/#webhook-receiver-)
documentation. The `url` must specify the IRC channel name that alerts should
be sent to:
```
send_resolved: false
url: http://localhost:8000/mychannel
```
alertmanager-irc-relay-0.5.1/backoff.go 0000664 0000000 0000000 00000005471 14754146416 0020011 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"context"
"math"
"math/rand"
"time"
"github.com/google/alertmanager-irc-relay/logging"
)
type JitterFunc func(int) int
type DelayerMaker interface {
NewDelayer(float64, float64, time.Duration) Delayer
}
type Delayer interface {
Delay()
DelayContext(context.Context) bool
}
type Backoff struct {
step float64
maxBackoff float64
resetDelta float64
lastAttempt time.Time
durationUnit time.Duration
jitterer JitterFunc
timeTeller TimeTeller
}
func jitterFunc(input int) int {
if input == 0 {
return 0
}
return rand.Intn(input)
}
type BackoffMaker struct{}
func (bm *BackoffMaker) NewDelayer(maxBackoff float64, resetDelta float64, durationUnit time.Duration) Delayer {
timeTeller := &RealTime{}
return NewBackoffForTesting(
maxBackoff, resetDelta, durationUnit, jitterFunc, timeTeller)
}
func NewBackoffForTesting(maxBackoff float64, resetDelta float64,
durationUnit time.Duration, jitterer JitterFunc, timeTeller TimeTeller) *Backoff {
return &Backoff{
step: 0,
maxBackoff: maxBackoff,
resetDelta: resetDelta,
lastAttempt: timeTeller.Now(),
durationUnit: durationUnit,
jitterer: jitterer,
timeTeller: timeTeller,
}
}
func (b *Backoff) maybeReset() {
now := b.timeTeller.Now()
lastAttemptDelta := float64(now.Sub(b.lastAttempt) / b.durationUnit)
b.lastAttempt = now
if lastAttemptDelta >= b.resetDelta {
b.step = 0
}
}
func (b *Backoff) GetDelay() time.Duration {
b.maybeReset()
var synchronizedDuration float64
// Do not add any delay the first time.
if b.step == 0 {
synchronizedDuration = 0
} else {
synchronizedDuration = math.Pow(2, b.step)
}
if synchronizedDuration < b.maxBackoff {
b.step++
} else {
synchronizedDuration = b.maxBackoff
}
duration := time.Duration(b.jitterer(int(synchronizedDuration)))
return duration * b.durationUnit
}
func (b *Backoff) Delay() {
b.DelayContext(context.Background())
}
func (b *Backoff) DelayContext(ctx context.Context) bool {
delay := b.GetDelay()
logging.Info("Backoff for %s starts", delay)
select {
case <-b.timeTeller.After(delay):
logging.Info("Backoff for %s ends", delay)
case <-ctx.Done():
logging.Info("Backoff for %s canceled by context", delay)
return false
}
return true
}
alertmanager-irc-relay-0.5.1/backoff_test.go 0000664 0000000 0000000 00000004552 14754146416 0021047 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"context"
"testing"
"time"
)
func FakeJitter(input int) int {
return input
}
func MakeTestingBackoff(maxBackoff float64, resetDelta float64, elapsedTime []int) (*Backoff, *FakeTime) {
fakeTime := &FakeTime{
timeseries: elapsedTime,
lastIndex: 0,
durationUnit: time.Millisecond,
afterChan: make(chan time.Time, 1),
}
backoff := NewBackoffForTesting(maxBackoff, resetDelta, time.Millisecond,
FakeJitter, fakeTime)
return backoff, fakeTime
}
func RunBackoffTest(t *testing.T, maxBackoff float64, resetDelta float64, elapsedTime []int, expectedDelays []int) {
backoff, _ := MakeTestingBackoff(maxBackoff, resetDelta, elapsedTime)
for i, value := range expectedDelays {
expected_delay := time.Duration(value) * time.Millisecond
delay := backoff.GetDelay()
if expected_delay != delay {
t.Errorf("Call #%d of GetDelay returned %s (expected %s)",
i, delay, expected_delay)
}
}
}
func TestBackoffIncreasesAndReachesMax(t *testing.T) {
RunBackoffTest(t,
8,
32,
// Simple sequential time
[]int{0, 0, 1, 2, 3, 4, 5, 6, 7},
// Exponential ramp-up to max, then keep max.
[]int{0, 2, 4, 8, 8, 8, 8, 8},
)
}
func TestBackoffReset(t *testing.T) {
RunBackoffTest(t,
8,
32,
// Simulate two intervals bigger than resetDelta
[]int{0, 0, 1, 2, 50, 51, 100, 101, 102},
// Delays get reset each time
[]int{0, 2, 4, 0, 2, 0, 2, 4},
)
}
func TestBackoffDelayContext(t *testing.T) {
backoff, fakeTime := MakeTestingBackoff(8, 32, []int{0, 0, 0})
ctx, cancel := context.WithCancel(context.Background())
fakeTime.afterChan <- time.Now()
if ok := backoff.DelayContext(ctx); !ok {
t.Errorf("Expired time does not return true")
}
cancel()
if ok := backoff.DelayContext(ctx); ok {
t.Errorf("Canceled context does not return false")
}
}
alertmanager-irc-relay-0.5.1/config.go 0000664 0000000 0000000 00000006510 14754146416 0017656 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"github.com/google/alertmanager-irc-relay/logging"
)
const (
defaultMsgOnceTemplate = "Alert {{ .GroupLabels.alertname }} for {{ .GroupLabels.job }} is {{ .Status }}"
defaultMsgTemplate = "Alert {{ .Labels.alertname }} on {{ .Labels.instance }} is {{ .Status }}"
)
type IRCChannel struct {
Name string `yaml:"name"`
Password string `yaml:"password"`
}
type Config struct {
HTTPHost string `yaml:"http_host"`
HTTPPort int `yaml:"http_port"`
IRCNick string `yaml:"irc_nickname"`
IRCNickPass string `yaml:"irc_nickname_password"`
IRCRealName string `yaml:"irc_realname"`
IRCHost string `yaml:"irc_host"`
IRCPort int `yaml:"irc_port"`
IRCHostPass string `yaml:"irc_host_password"`
IRCUseSSL bool `yaml:"irc_use_ssl"`
IRCVerifySSL bool `yaml:"irc_verify_ssl"`
IRCChannels []IRCChannel `yaml:"irc_channels"`
MsgTemplate string `yaml:"msg_template"`
MsgOnce bool `yaml:"msg_once_per_alert_group"`
UsePrivmsg bool `yaml:"use_privmsg"`
AlertBufferSize int `yaml:"alert_buffer_size"`
NickservName string `yaml:"nickserv_name"`
NickservIdentifyPatterns []string `yaml:"nickserv_identify_patterns"`
ChanservName string `yaml:"chanserv_name"`
}
func LoadConfig(configFile string) (*Config, error) {
config := &Config{
HTTPHost: "localhost",
HTTPPort: 8000,
IRCNick: "alertmanager-irc-relay",
IRCNickPass: "",
IRCRealName: "Alertmanager IRC Relay",
IRCHost: "example.com",
IRCPort: 7000,
IRCHostPass: "",
IRCUseSSL: true,
IRCVerifySSL: true,
IRCChannels: []IRCChannel{},
MsgOnce: false,
UsePrivmsg: false,
AlertBufferSize: 2048,
NickservName: "NickServ",
NickservIdentifyPatterns: []string{
"Please choose a different nickname, or identify via",
"identify via /msg NickServ identify ",
"type /msg NickServ IDENTIFY password",
"authenticate yourself to services with the IDENTIFY command",
},
ChanservName: "ChanServ",
}
if configFile != "" {
data, err := ioutil.ReadFile(configFile)
if err != nil {
return nil, err
}
data = []byte(os.ExpandEnv(string(data)))
if err := yaml.Unmarshal(data, config); err != nil {
return nil, err
}
}
// Set default template if config does not have one.
if config.MsgTemplate == "" {
if config.MsgOnce {
config.MsgTemplate = defaultMsgOnceTemplate
} else {
config.MsgTemplate = defaultMsgTemplate
}
}
loadedConfig, _ := yaml.Marshal(config)
logging.Debug("Loaded config:\n%s", loadedConfig)
return config, nil
}
alertmanager-irc-relay-0.5.1/config_test.go 0000664 0000000 0000000 00000013355 14754146416 0020722 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"fmt"
"io/ioutil"
"os"
"testing"
"gopkg.in/yaml.v2"
)
func TestNoConfig(t *testing.T) {
noConfigFile := ""
config, err := LoadConfig(noConfigFile)
if config == nil {
t.Errorf("Expected a default config, got: %s", err)
}
}
func TestLoadGoodConfig(t *testing.T) {
expectedConfig := &Config{
HTTPHost: "test.web",
HTTPPort: 8888,
IRCNick: "foo",
IRCHost: "irc.example.com",
IRCPort: 1234,
IRCHostPass: "hostsecret",
IRCUseSSL: true,
IRCChannels: []IRCChannel{IRCChannel{Name: "#foobar"}},
MsgTemplate: defaultMsgTemplate,
MsgOnce: false,
UsePrivmsg: false,
AlertBufferSize: 666,
}
expectedData, err := yaml.Marshal(expectedConfig)
if err != nil {
t.Errorf("Could not serialize test data: %s", err)
}
tmpfile, err := ioutil.TempFile("", "airtestconfig")
if err != nil {
t.Errorf("Could not create tmpfile for testing: %s", err)
}
defer os.Remove(tmpfile.Name())
if _, err := tmpfile.Write(expectedData); err != nil {
t.Errorf("Could not write test data in tmpfile: %s", err)
}
if err := tmpfile.Close(); err != nil {
t.Errorf("Could not close tmpfile: %s", err)
}
config, err := LoadConfig(tmpfile.Name())
if config == nil {
t.Errorf("Expected a config, got: %s", err)
}
configData, err := yaml.Marshal(config)
if err != nil {
t.Errorf("Could not serialize loaded config")
}
if string(expectedData) != string(configData) {
t.Errorf("Loaded config does not match expected config: %s", configData)
}
}
func TestLoadWithEnvironmentVariables(t *testing.T) {
expectedNickPass := "mynickpass"
os.Setenv("NICKSERV_PASSWORD", expectedNickPass)
defer os.Clearenv()
tmpfile, err := ioutil.TempFile("", "airtestenvvarconfig")
if err != nil {
t.Errorf("Could not create tmpfile for testing: %s", err)
}
defer os.Remove(tmpfile.Name())
msgOnceConfigData := []byte("irc_nickname_password: $NICKSERV_PASSWORD")
if _, err := tmpfile.Write(msgOnceConfigData); err != nil {
t.Errorf("Could not write test data in tmpfile: %s", err)
}
tmpfile.Close()
config, err := LoadConfig(tmpfile.Name())
if config == nil {
t.Errorf("Expected a config, got: %s", err)
}
if config.IRCNickPass != expectedNickPass {
t.Errorf("Loaded unexpected value: %s (expected: %s)",
config.IRCNickPass, expectedNickPass)
}
}
func TestLoadBadFile(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "airtestbadfile")
if err != nil {
t.Errorf("Could not create tmpfile for testing: %s", err)
}
tmpfile.Close()
os.Remove(tmpfile.Name())
config, err := LoadConfig(tmpfile.Name())
if err == nil || config != nil {
t.Errorf("Expected no config upon non-existent file. err: %s", err)
}
}
func TestLoadBadConfig(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "airtestbadconfig")
if err != nil {
t.Errorf("Could not create tmpfile for testing: %s", err)
}
defer os.Remove(tmpfile.Name())
badConfigData := []byte("footest\nbarbaz\n")
if _, err := tmpfile.Write(badConfigData); err != nil {
t.Errorf("Could not write test data in tmpfile: %s", err)
}
tmpfile.Close()
config, err := LoadConfig(tmpfile.Name())
if err == nil || config != nil {
t.Errorf("Expected no config upon bad config. err: %s", err)
}
}
func TestMsgOnceDefaultTemplate(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "airtesttemmplateonceconfig")
if err != nil {
t.Errorf("Could not create tmpfile for testing: %s", err)
}
defer os.Remove(tmpfile.Name())
msgOnceConfigData := []byte("msg_once_per_alert_group: yes")
if _, err := tmpfile.Write(msgOnceConfigData); err != nil {
t.Errorf("Could not write test data in tmpfile: %s", err)
}
tmpfile.Close()
config, err := LoadConfig(tmpfile.Name())
if config == nil {
t.Errorf("Expected a config, got: %s", err)
}
if config.MsgTemplate != defaultMsgOnceTemplate {
t.Errorf("Expecting defaultMsgOnceTemplate when MsgOnce is true")
}
}
func TestMsgDefaultTemplate(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "airtesttemmplateconfig")
if err != nil {
t.Errorf("Could not create tmpfile for testing: %s", err)
}
defer os.Remove(tmpfile.Name())
if _, err := tmpfile.Write([]byte("")); err != nil {
t.Errorf("Could not write test data in tmpfile: %s", err)
}
tmpfile.Close()
config, err := LoadConfig(tmpfile.Name())
if config == nil {
t.Errorf("Expected a config, got: %s", err)
}
if config.MsgTemplate != defaultMsgTemplate {
t.Errorf("Expecting defaultMsgTemplate when MsgOnce is false")
}
}
func TestGivenTemplateNotOverwritten(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "airtestexpectedtemmplate")
if err != nil {
t.Errorf("Could not create tmpfile for testing: %s", err)
}
defer os.Remove(tmpfile.Name())
expectedTemplate := "Alert {{ .Status }}: {{ .Annotations.SUMMARY }}"
configData := []byte(fmt.Sprintf("msg_template: \"%s\"", expectedTemplate))
if _, err := tmpfile.Write(configData); err != nil {
t.Errorf("Could not write test data in tmpfile: %s", err)
}
tmpfile.Close()
config, err := LoadConfig(tmpfile.Name())
if config == nil {
t.Errorf("Expected a config, got: %s", err)
}
if config.MsgTemplate != expectedTemplate {
t.Errorf("Template does not match configuration")
}
}
alertmanager-irc-relay-0.5.1/context.go 0000664 0000000 0000000 00000002401 14754146416 0020070 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google LLC
//
// 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
//
// https://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 main
import (
"context"
"os"
"os/signal"
"sync"
"github.com/google/alertmanager-irc-relay/logging"
)
func WithSignal(ctx context.Context, s ...os.Signal) (context.Context, context.CancelFunc) {
sigCtx, cancel := context.WithCancel(ctx)
c := make(chan os.Signal, 1)
signal.Notify(c, s...)
go func() {
select {
case <-c:
logging.Info("Received %s, exiting", s)
cancel()
case <-ctx.Done():
cancel()
}
signal.Stop(c)
}()
return sigCtx, cancel
}
func WithWaitGroup(ctx context.Context, wg *sync.WaitGroup) context.Context {
wgCtx, cancel := context.WithCancel(context.Background())
go func() {
<-ctx.Done()
wg.Wait()
cancel()
}()
return wgCtx
}
alertmanager-irc-relay-0.5.1/data.go 0000664 0000000 0000000 00000001213 14754146416 0017315 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
type AlertMsg struct {
Channel, Alert string
}
alertmanager-irc-relay-0.5.1/fake_delayer.go 0000664 0000000 0000000 00000002365 14754146416 0021030 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google LLC
//
// 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
//
// https://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 main
import (
"context"
"time"
"github.com/google/alertmanager-irc-relay/logging"
)
type FakeDelayerMaker struct{}
func (fdm *FakeDelayerMaker) NewDelayer(_ float64, _ float64, _ time.Duration) Delayer {
return &FakeDelayer{
DelayOnChan: false,
StopDelay: make(chan bool),
}
}
type FakeDelayer struct {
DelayOnChan bool
StopDelay chan bool
}
func (f *FakeDelayer) Delay() {
f.DelayContext(context.Background())
}
func (f *FakeDelayer) DelayContext(ctx context.Context) bool {
logging.Info("Faking Backoff")
if f.DelayOnChan {
logging.Info("Waiting StopDelay signal")
<-f.StopDelay
logging.Info("Received StopDelay signal")
}
return true
}
alertmanager-irc-relay-0.5.1/fake_timeteller.go 0000664 0000000 0000000 00000001767 14754146416 0021556 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google LLC
//
// 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
//
// https://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 main
import (
"time"
)
type FakeTime struct {
timeseries []int
lastIndex int
durationUnit time.Duration
afterChan chan time.Time
}
func (f *FakeTime) Now() time.Time {
timeDelta := time.Duration(f.timeseries[f.lastIndex]) * f.durationUnit
fakeTime := time.Unix(0, 0).Add(timeDelta)
f.lastIndex++
return fakeTime
}
func (f *FakeTime) After(d time.Duration) <-chan time.Time {
return f.afterChan
}
alertmanager-irc-relay-0.5.1/format.go 0000664 0000000 0000000 00000004620 14754146416 0017701 0 ustar 00root root 0000000 0000000 // Copyright 2020 Google LLC
//
// 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
//
// https://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 main
import (
"bytes"
"encoding/json"
"net/url"
"strings"
"text/template"
"github.com/google/alertmanager-irc-relay/logging"
promtmpl "github.com/prometheus/alertmanager/template"
)
type Formatter struct {
MsgTemplate *template.Template
MsgOnce bool
}
func NewFormatter(config *Config) (*Formatter, error) {
funcMap := template.FuncMap{
"ToUpper": strings.ToUpper,
"ToLower": strings.ToLower,
"Join": strings.Join,
"QueryEscape": url.QueryEscape,
"PathEscape": url.PathEscape,
}
tmpl, err := template.New("msg").Funcs(funcMap).Parse(config.MsgTemplate)
if err != nil {
return nil, err
}
return &Formatter{
MsgTemplate: tmpl,
MsgOnce: config.MsgOnce,
}, nil
}
func (f *Formatter) FormatMsg(ircChannel string, data interface{}) []string {
output := bytes.Buffer{}
var msg string
if err := f.MsgTemplate.Execute(&output, data); err != nil {
msg_bytes, _ := json.Marshal(data)
msg = string(msg_bytes)
logging.Error("Could not apply msg template on alert (%s): %s",
err, msg)
logging.Warn("Sending raw alert")
alertHandlingErrors.WithLabelValues(ircChannel, "format_msg").Inc()
} else {
msg = output.String()
}
// Do not send to IRC messages with newlines, split in multiple messages instead.
newLinesSplit := func(r rune) bool {
return r == '\n' || r == '\r'
}
return strings.FieldsFunc(msg, newLinesSplit)
}
func (f *Formatter) GetMsgsFromAlertMessage(ircChannel string,
data *promtmpl.Data) []AlertMsg {
msgs := []AlertMsg{}
if f.MsgOnce {
for _, msg := range f.FormatMsg(ircChannel, data) {
msgs = append(msgs,
AlertMsg{Channel: ircChannel, Alert: msg})
}
} else {
for _, alert := range data.Alerts {
for _, msg := range f.FormatMsg(ircChannel, alert) {
msgs = append(msgs,
AlertMsg{Channel: ircChannel, Alert: msg})
}
}
}
return msgs
}
alertmanager-irc-relay-0.5.1/format_test.go 0000664 0000000 0000000 00000010171 14754146416 0020736 0 ustar 00root root 0000000 0000000 // Copyright 2020 Google LLC
//
// 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
//
// https://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 main
import (
"encoding/json"
"fmt"
"reflect"
"testing"
promtmpl "github.com/prometheus/alertmanager/template"
)
func CreateFormatterAndCheckOutput(t *testing.T, c *Config, expected []AlertMsg) {
f, _ := NewFormatter(c)
var alertMessage = promtmpl.Data{}
if err := json.Unmarshal([]byte(testdataSimpleAlertJson), &alertMessage); err != nil {
t.Fatal(fmt.Sprintf("Could not unmarshal %s", testdataSimpleAlertJson))
}
alertMsgs := f.GetMsgsFromAlertMessage("#somechannel", &alertMessage)
if !reflect.DeepEqual(expected, alertMsgs) {
t.Error(fmt.Sprintf(
"Unexpected alert msg.\nExpected: %s\nActual: %s",
expected, alertMsgs))
}
}
func TestTemplateErrorsCreateRawAlertMsg(t *testing.T) {
testingConfig := Config{MsgTemplate: "Bogus template {{ nil }}"}
expectedAlertMsgs := []AlertMsg{
AlertMsg{
Channel: "#somechannel",
Alert: `{"status":"resolved","labels":{"alertname":"airDown","instance":"instance1:3456","job":"air","service":"prometheus","severity":"ticket","zone":"global"},"annotations":{"DESCRIPTION":"service /prometheus has irc gateway down on instance1","SUMMARY":"service /prometheus air down on instance1"},"startsAt":"2017-05-15T13:49:37.834Z","endsAt":"2017-05-15T13:50:37.835Z","generatorURL":"https://prometheus.example.com/prometheus/...","fingerprint":"66214a361160fb6f"}`,
},
AlertMsg{
Channel: "#somechannel",
Alert: `{"status":"resolved","labels":{"alertname":"airDown","instance":"instance2:7890","job":"air","service":"prometheus","severity":"ticket","zone":"global"},"annotations":{"DESCRIPTION":"service /prometheus has irc gateway down on instance2","SUMMARY":"service /prometheus air down on instance2"},"startsAt":"2017-05-15T11:47:37.834Z","endsAt":"2017-05-15T11:48:37.834Z","generatorURL":"https://prometheus.example.com/prometheus/...","fingerprint":"25a874c99325d1ce"}`,
},
}
CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs)
}
func TestAlertsDispatchedOnce(t *testing.T) {
testingConfig := Config{
MsgTemplate: "Alert {{ .GroupLabels.alertname }} is {{ .Status }}",
MsgOnce: true,
}
expectedAlertMsgs := []AlertMsg{
AlertMsg{
Channel: "#somechannel",
Alert: "Alert airDown is resolved",
},
}
CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs)
}
func TestStringsFunctions(t *testing.T) {
testingConfig := Config{
MsgTemplate: "Alert {{ .GroupLabels.alertname | ToUpper }} is {{ .Status }}",
MsgOnce: true,
}
expectedAlertMsgs := []AlertMsg{
AlertMsg{
Channel: "#somechannel",
Alert: "Alert AIRDOWN is resolved",
},
}
CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs)
}
func TestUrlFunctions(t *testing.T) {
testingConfig := Config{
MsgTemplate: "{{ .Annotations.SUMMARY | PathEscape }}",
}
expectedAlertMsgs := []AlertMsg{
AlertMsg{
Channel: "#somechannel",
Alert: "service%20%2Fprometheus%20air%20down%20on%20instance1",
},
AlertMsg{
Channel: "#somechannel",
Alert: "service%20%2Fprometheus%20air%20down%20on%20instance2",
},
}
CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs)
}
func TestMultilineTemplates(t *testing.T) {
testingConfig := Config{
MsgTemplate: "Alert {{ .GroupLabels.alertname }}\nis\r{{ .Status }}",
MsgOnce: true,
}
expectedAlertMsgs := []AlertMsg{
AlertMsg{
Channel: "#somechannel",
Alert: "Alert airDown",
},
AlertMsg{
Channel: "#somechannel",
Alert: "is",
},
AlertMsg{
Channel: "#somechannel",
Alert: "resolved",
},
}
CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs)
}
alertmanager-irc-relay-0.5.1/go.mod 0000664 0000000 0000000 00000000370 14754146416 0017166 0 ustar 00root root 0000000 0000000 module github.com/google/alertmanager-irc-relay
go 1.13
require (
github.com/fluffle/goirc v1.1.1
github.com/gorilla/mux v1.8.0
github.com/prometheus/alertmanager v0.21.0
github.com/prometheus/client_golang v1.11.1
gopkg.in/yaml.v2 v2.4.0
)
alertmanager-irc-relay-0.5.1/go.sum 0000664 0000000 0000000 00000166722 14754146416 0017231 0 ustar 00root root 0000000 0000000 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fluffle/goirc v1.1.1 h1:6nO+7rrED3Kp3mngoi9OmQmQHevNwDfjGpYUdWc1s0k=
github.com/fluffle/goirc v1.1.1/go.mod h1:iRzPLv2vkuZEtbns5LioYguJkRh/bvshuWg7izf1yeE=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/alertmanager v0.21.0 h1:qK51JcUR9l/unhawGA9F9B64OCYfcGewhPNprem/Acc=
github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97 h1:DAuln/hGp+aJiHpID1Y1hYzMEPP5WLwtZHPb50mN0OE=
golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
alertmanager-irc-relay-0.5.1/http.go 0000664 0000000 0000000 00000010035 14754146416 0017365 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
"github.com/google/alertmanager-irc-relay/logging"
"github.com/gorilla/mux"
promtmpl "github.com/prometheus/alertmanager/template"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
handledAlertGroups = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "webhook_handled_alert_groups",
Help: "Number of alert groups received"},
[]string{"ircchannel"},
)
handledAlerts = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "webhook_handled_alerts",
Help: "Number of single alert messages relayed"},
[]string{"ircchannel"},
)
alertHandlingErrors = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "webhook_alert_handling_errors",
Help: "Errors while processing webhook requests"},
[]string{"ircchannel", "error"},
)
)
type HTTPListener func(string, http.Handler) error
type HTTPServer struct {
Addr string
Port int
formatter *Formatter
AlertMsgs chan AlertMsg
httpListener HTTPListener
}
func NewHTTPServer(config *Config, alertMsgs chan AlertMsg) (
*HTTPServer, error) {
return NewHTTPServerForTesting(config, alertMsgs, http.ListenAndServe)
}
func NewHTTPServerForTesting(config *Config, alertMsgs chan AlertMsg,
httpListener HTTPListener) (*HTTPServer, error) {
formatter, err := NewFormatter(config)
if err != nil {
return nil, err
}
server := &HTTPServer{
Addr: config.HTTPHost,
Port: config.HTTPPort,
formatter: formatter,
AlertMsgs: alertMsgs,
httpListener: httpListener,
}
return server, nil
}
func (s *HTTPServer) RelayAlert(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
ircChannel := "#" + vars["IRCChannel"]
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1024*1024*1024))
if err != nil {
logging.Error("Could not get body: %s", err)
alertHandlingErrors.WithLabelValues(ircChannel, "read_body").Inc()
return
}
var alertMessage = promtmpl.Data{}
if err := json.Unmarshal(body, &alertMessage); err != nil {
logging.Error("Could not decode request body (%s): %s", err, body)
alertHandlingErrors.WithLabelValues(ircChannel, "decode_body").Inc()
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(422) // Unprocessable entity
if err := json.NewEncoder(w).Encode(err); err != nil {
logging.Error("Could not write decoding error: %s", err)
return
}
return
}
handledAlertGroups.WithLabelValues(ircChannel).Inc()
for _, alertMsg := range s.formatter.GetMsgsFromAlertMessage(
ircChannel, &alertMessage) {
select {
case s.AlertMsgs <- alertMsg:
handledAlerts.WithLabelValues(ircChannel).Inc()
default:
logging.Error("Could not send this alert to the IRC routine: %s",
alertMsg)
alertHandlingErrors.WithLabelValues(ircChannel, "internal_comm_channel_full").Inc()
}
}
}
func (s *HTTPServer) Run() {
router := mux.NewRouter().StrictSlash(true)
router.Path("/metrics").Handler(promhttp.Handler())
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.RelayAlert(w, r)
})
router.Path("/{IRCChannel}").Handler(handler).Methods("POST")
listenAddr := strings.Join(
[]string{s.Addr, strconv.Itoa(s.Port)}, ":")
logging.Info("Starting HTTP server")
if err := s.httpListener(listenAddr, router); err != nil {
logging.Error("Could not start http server: %s", err)
}
}
alertmanager-irc-relay-0.5.1/http_test.go 0000664 0000000 0000000 00000007511 14754146416 0020431 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
)
type FakeHTTPListener struct {
StartedServing chan bool
StopServing chan bool
AlertMsgs chan AlertMsg // kinda ugly putting it here, but convenient
router http.Handler
}
func (listener *FakeHTTPListener) Serve(_ string, router http.Handler) error {
listener.router = router
listener.StartedServing <- true
<-listener.StopServing
return nil
}
func NewFakeHTTPListener() *FakeHTTPListener {
return &FakeHTTPListener{
StartedServing: make(chan bool),
StopServing: make(chan bool),
AlertMsgs: make(chan AlertMsg, 10),
}
}
func MakeHTTPTestingConfig() *Config {
return &Config{
HTTPHost: "test.web",
HTTPPort: 8888,
MsgTemplate: "Alert {{ .Labels.alertname }} on {{ .Labels.instance }} is {{ .Status }}",
}
}
func RunHTTPTest(t *testing.T,
alertData string, url string,
testingConfig *Config, listener *FakeHTTPListener) *http.Response {
httpServer, err := NewHTTPServerForTesting(testingConfig,
listener.AlertMsgs, listener.Serve)
if err != nil {
t.Fatal(fmt.Sprintf("Could not create HTTP server: %s", err))
}
go httpServer.Run()
<-listener.StartedServing
alertDataReader := strings.NewReader(alertData)
request, err := http.NewRequest("POST", url, alertDataReader)
if err != nil {
t.Fatal(fmt.Sprintf("Could not create HTTP request: %s", err))
}
responseRecorder := httptest.NewRecorder()
listener.router.ServeHTTP(responseRecorder, request)
listener.StopServing <- true
return responseRecorder.Result()
}
func TestAlertsDispatched(t *testing.T) {
listener := NewFakeHTTPListener()
testingConfig := MakeHTTPTestingConfig()
expectedAlertMsgs := []AlertMsg{
AlertMsg{
Channel: "#somechannel",
Alert: "Alert airDown on instance1:3456 is resolved",
},
AlertMsg{
Channel: "#somechannel",
Alert: "Alert airDown on instance2:7890 is resolved",
},
}
expectedStatusCode := 200
response := RunHTTPTest(
t, testdataSimpleAlertJson, "/somechannel",
testingConfig, listener)
if expectedStatusCode != response.StatusCode {
t.Error(fmt.Sprintf("Expected %d status in response, got %d",
expectedStatusCode, response.StatusCode))
}
for _, expectedAlertMsg := range expectedAlertMsgs {
alertMsg := <-listener.AlertMsgs
if !reflect.DeepEqual(expectedAlertMsg, alertMsg) {
t.Error(fmt.Sprintf(
"Unexpected alert msg.\nExpected: %s\nActual: %s",
expectedAlertMsg, alertMsg))
}
}
}
func TestRootReturnsError(t *testing.T) {
listener := NewFakeHTTPListener()
testingConfig := MakeHTTPTestingConfig()
expectedStatusCode := 404
response := RunHTTPTest(
t, testdataSimpleAlertJson, "/",
testingConfig, listener)
if expectedStatusCode != response.StatusCode {
t.Error(fmt.Sprintf("Expected %d status in response, got %d",
expectedStatusCode, response.StatusCode))
}
}
func TestInvalidDataReturnsError(t *testing.T) {
listener := NewFakeHTTPListener()
testingConfig := MakeHTTPTestingConfig()
expectedStatusCode := 422
response := RunHTTPTest(
t, testdataBogusAlertJson, "/somechannel",
testingConfig, listener)
if expectedStatusCode != response.StatusCode {
t.Error(fmt.Sprintf("Expected %d status in response, got %d",
expectedStatusCode, response.StatusCode))
}
}
alertmanager-irc-relay-0.5.1/irc.go 0000664 0000000 0000000 00000022774 14754146416 0017200 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"context"
"crypto/tls"
"strconv"
"strings"
"sync"
"time"
irc "github.com/fluffle/goirc/client"
"github.com/google/alertmanager-irc-relay/logging"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
const (
pingFrequencySecs = 60
connectionTimeoutSecs = 30
nickservWaitSecs = 10
ircConnectMaxBackoffSecs = 300
ircConnectBackoffResetSecs = 1800
)
var (
ircConnectedGauge = promauto.NewGauge(prometheus.GaugeOpts{
Name: "irc_connected",
Help: "Whether the IRC connection is established",
})
ircSentMsgs = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "irc_sent_msgs",
Help: "Number of IRC messages sent"},
[]string{"ircchannel"},
)
ircSendMsgErrors = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "irc_send_msg_errors",
Help: "Errors while sending IRC messages"},
[]string{"ircchannel", "error"},
)
)
func loggerHandler(_ *irc.Conn, line *irc.Line) {
logging.Info("Received: '%s'", line.Raw)
}
func makeGOIRCConfig(config *Config) *irc.Config {
ircConfig := irc.NewConfig(config.IRCNick)
ircConfig.Me.Ident = config.IRCNick
ircConfig.Me.Name = config.IRCRealName
ircConfig.Server = strings.Join(
[]string{config.IRCHost, strconv.Itoa(config.IRCPort)}, ":")
ircConfig.Pass = config.IRCHostPass
ircConfig.SSL = config.IRCUseSSL
ircConfig.SSLConfig = &tls.Config{
ServerName: config.IRCHost,
InsecureSkipVerify: !config.IRCVerifySSL,
}
ircConfig.PingFreq = pingFrequencySecs * time.Second
ircConfig.Timeout = connectionTimeoutSecs * time.Second
ircConfig.NewNick = func(n string) string { return n + "^" }
return ircConfig
}
type IRCNotifier struct {
// Nick stores the nickname specified in the config, because irc.Client
// might change its copy.
Nick string
NickPassword string
NickservName string
NickservIdentifyPatterns []string
Client *irc.Conn
AlertMsgs chan AlertMsg
// irc.Conn has a Connected() method that can tell us wether the TCP
// connection is up, and thus if we should trigger connect/disconnect.
// We need to track the session establishment also at a higher level to
// understand when the server has accepted us and thus when we can join
// channels, send notices, etc.
sessionUp bool
sessionUpSignal chan bool
sessionDownSignal chan bool
sessionWg sync.WaitGroup
channelReconciler *ChannelReconciler
UsePrivmsg bool
NickservDelayWait time.Duration
BackoffCounter Delayer
timeTeller TimeTeller
}
func NewIRCNotifier(config *Config, alertMsgs chan AlertMsg, delayerMaker DelayerMaker, timeTeller TimeTeller) (*IRCNotifier, error) {
ircConfig := makeGOIRCConfig(config)
client := irc.Client(ircConfig)
backoffCounter := delayerMaker.NewDelayer(
ircConnectMaxBackoffSecs, ircConnectBackoffResetSecs,
time.Second)
channelReconciler := NewChannelReconciler(config, client, delayerMaker, timeTeller)
notifier := &IRCNotifier{
Nick: config.IRCNick,
NickPassword: config.IRCNickPass,
NickservName: config.NickservName,
NickservIdentifyPatterns: config.NickservIdentifyPatterns,
Client: client,
AlertMsgs: alertMsgs,
sessionUpSignal: make(chan bool),
sessionDownSignal: make(chan bool),
channelReconciler: channelReconciler,
UsePrivmsg: config.UsePrivmsg,
NickservDelayWait: nickservWaitSecs * time.Second,
BackoffCounter: backoffCounter,
timeTeller: timeTeller,
}
notifier.registerHandlers()
return notifier, nil
}
func (n *IRCNotifier) registerHandlers() {
n.Client.HandleFunc(irc.CONNECTED,
func(*irc.Conn, *irc.Line) {
logging.Info("Session established")
n.sessionUpSignal <- true
})
n.Client.HandleFunc(irc.DISCONNECTED,
func(*irc.Conn, *irc.Line) {
logging.Info("Disconnected from IRC")
n.sessionDownSignal <- false
})
n.Client.HandleFunc(irc.NOTICE,
func(_ *irc.Conn, line *irc.Line) {
n.HandleNotice(line.Nick, line.Text())
})
for _, event := range []string{"433"} {
n.Client.HandleFunc(event, loggerHandler)
}
}
func (n *IRCNotifier) HandleNotice(nick string, msg string) {
logging.Info("Received NOTICE from %s: %s", nick, msg)
if strings.ToLower(nick) == "nickserv" {
n.HandleNickservMsg(msg)
}
}
func (n *IRCNotifier) HandleNickservMsg(msg string) {
if n.NickPassword == "" {
logging.Debug("Skip processing NickServ request, no password configured")
return
}
// Remove most common formatting options from NickServ messages
cleaner := strings.NewReplacer(
"\001", "", // bold
"\002", "", // faint
"\004", "", // underline
"\037", "", // underline
)
cleanedMsg := cleaner.Replace(msg)
for _, identifyPattern := range n.NickservIdentifyPatterns {
logging.Debug("Checking if NickServ message matches identify request '%s'", identifyPattern)
if strings.Contains(cleanedMsg, identifyPattern) {
logging.Info("Handling NickServ request to IDENTIFY")
n.Client.Privmsgf(n.NickservName, "IDENTIFY %s", n.NickPassword)
return
}
}
}
func (n *IRCNotifier) MaybeGhostNick() {
if n.NickPassword == "" {
logging.Debug("Skip GHOST check, no password configured")
return
}
currentNick := n.Client.Me().Nick
if currentNick != n.Nick {
logging.Info("My nick is '%s', sending GHOST to NickServ to get '%s'",
currentNick, n.Nick)
n.Client.Privmsgf(n.NickservName, "GHOST %s %s", n.Nick,
n.NickPassword)
time.Sleep(n.NickservDelayWait)
logging.Info("Changing nick to '%s'", n.Nick)
n.Client.Nick(n.Nick)
time.Sleep(n.NickservDelayWait)
}
}
func (n *IRCNotifier) MaybeWaitForNickserv() {
if n.NickPassword == "" {
logging.Debug("Skip NickServ wait, no password configured")
return
}
// Very lazy/optimistic, but this is good enough for my irssi config,
// so it should work here as well.
logging.Info("Waiting for NickServ to notice us and issue an identify request")
time.Sleep(n.NickservDelayWait)
}
func (n *IRCNotifier) ChannelJoined(ctx context.Context, channel string) bool {
isJoined, waitJoined := n.channelReconciler.JoinChannel(channel)
if isJoined {
return true
}
select {
case <-waitJoined:
return true
case <-n.timeTeller.After(ircJoinWaitSecs * time.Second):
logging.Warn("Channel %s not joined after %d seconds, giving bad news to caller", channel, ircJoinWaitSecs)
return false
case <-ctx.Done():
logging.Info("Context canceled while waiting for join on channel %s", channel)
return false
}
}
func (n *IRCNotifier) SendAlertMsg(ctx context.Context, alertMsg *AlertMsg) {
if !n.sessionUp {
logging.Error("Cannot send alert to %s : IRC not connected", alertMsg.Channel)
ircSendMsgErrors.WithLabelValues(alertMsg.Channel, "not_connected").Inc()
return
}
if !n.ChannelJoined(ctx, alertMsg.Channel) {
logging.Error("Cannot send alert to %s : cannot join channel", alertMsg.Channel)
ircSendMsgErrors.WithLabelValues(alertMsg.Channel, "not_joined").Inc()
return
}
if n.UsePrivmsg {
n.Client.Privmsg(alertMsg.Channel, alertMsg.Alert)
} else {
n.Client.Notice(alertMsg.Channel, alertMsg.Alert)
}
ircSentMsgs.WithLabelValues(alertMsg.Channel).Inc()
}
func (n *IRCNotifier) ShutdownPhase() {
if n.sessionUp {
logging.Info("IRC client connected, quitting")
n.Client.Quit("see ya")
logging.Info("Wait for IRC disconnect to complete")
select {
case <-n.sessionDownSignal:
case <-n.timeTeller.After(n.Client.Config().Timeout):
logging.Warn("Timeout while waiting for IRC disconnect to complete, stopping anyway")
}
n.sessionWg.Done()
}
logging.Info("IRC shutdown complete")
}
func (n *IRCNotifier) ConnectedPhase(ctx context.Context) {
select {
case alertMsg := <-n.AlertMsgs:
n.SendAlertMsg(ctx, &alertMsg)
case <-n.sessionDownSignal:
n.sessionUp = false
n.sessionWg.Done()
n.channelReconciler.Stop()
n.Client.Quit("see ya")
ircConnectedGauge.Set(0)
case <-ctx.Done():
logging.Info("IRC routine asked to terminate")
}
}
func (n *IRCNotifier) SetupPhase(ctx context.Context) {
if !n.Client.Connected() {
logging.Info("Connecting to IRC %s", n.Client.Config().Server)
if ok := n.BackoffCounter.DelayContext(ctx); !ok {
return
}
if err := n.Client.ConnectContext(WithWaitGroup(ctx, &n.sessionWg)); err != nil {
logging.Error("Could not connect to IRC: %s", err)
return
}
logging.Info("Connected to IRC server, waiting to establish session")
}
select {
case <-n.sessionUpSignal:
n.sessionUp = true
n.sessionWg.Add(1)
n.MaybeGhostNick()
n.MaybeWaitForNickserv()
n.channelReconciler.Start(ctx)
ircConnectedGauge.Set(1)
case <-n.sessionDownSignal:
logging.Warn("Receiving a session down before the session is up, this is odd")
case <-ctx.Done():
logging.Info("IRC routine asked to terminate")
}
}
func (n *IRCNotifier) Run(ctx context.Context, stopWg *sync.WaitGroup) {
defer stopWg.Done()
for ctx.Err() != context.Canceled {
if !n.sessionUp {
n.SetupPhase(ctx)
} else {
n.ConnectedPhase(ctx)
}
}
n.ShutdownPhase()
}
alertmanager-irc-relay-0.5.1/irc_server_for_test.go 0000664 0000000 0000000 00000012142 14754146416 0022457 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google LLC
//
// 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
//
// https://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 main
import (
"bufio"
"errors"
"fmt"
"io"
"net"
"strings"
"sync"
"testing"
irc "github.com/fluffle/goirc/client"
"github.com/google/alertmanager-irc-relay/logging"
)
type LineHandlerFunc func(*bufio.ReadWriter, *irc.Line) error
func hJOIN(conn *bufio.ReadWriter, line *irc.Line) error {
r := fmt.Sprintf(":foo!foo@example.com JOIN :%s\n", line.Args[0])
_, err := conn.WriteString(r)
return err
}
func hUSER(conn *bufio.ReadWriter, line *irc.Line) error {
r := fmt.Sprintf(":example.com 001 %s :Welcome\n", line.Args[0])
_, err := conn.WriteString(r)
return err
}
func hQUIT(conn *bufio.ReadWriter, line *irc.Line) error {
return fmt.Errorf("client asked to terminate")
}
type closeEarlyHandler func()
type testServer struct {
net.Listener
Client net.Conn
ServingWaitGroup sync.WaitGroup
ConnectionsWaitGroup sync.WaitGroup
lineHandlersMu sync.Mutex
lineHandlers map[string]LineHandlerFunc
Log []string
closeEarlyMu sync.Mutex
closeEarlyHandler
}
func (s *testServer) setDefaultHandlers() {
if s.lineHandlers == nil {
s.lineHandlers = make(map[string]LineHandlerFunc)
}
s.lineHandlers["JOIN"] = hJOIN
s.lineHandlers["USER"] = hUSER
s.lineHandlers["QUIT"] = hQUIT
}
func (s *testServer) getHandler(cmd string) LineHandlerFunc {
s.lineHandlersMu.Lock()
defer s.lineHandlersMu.Unlock()
return s.lineHandlers[cmd]
}
func (s *testServer) SetHandler(cmd string, h LineHandlerFunc) {
s.lineHandlersMu.Lock()
defer s.lineHandlersMu.Unlock()
if h == nil {
delete(s.lineHandlers, cmd)
} else {
s.lineHandlers[cmd] = h
}
}
func (s *testServer) handleLine(conn *bufio.ReadWriter, line *irc.Line) error {
s.Log = append(s.Log, strings.Trim(line.Raw, " \r\n"))
handler := s.getHandler(line.Cmd)
if handler == nil {
logging.Info("=Server= No handler for command '%s', skipping", line.Cmd)
return nil
}
return handler(conn, line)
}
func (s *testServer) handleConnection(conn net.Conn) {
defer func() {
s.Client = nil
conn.Close()
s.ConnectionsWaitGroup.Done()
}()
bufConn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
for {
msg, err := bufConn.ReadBytes('\n')
if err != nil {
if err == io.EOF {
logging.Info("=Server= Client %s disconnected", conn.RemoteAddr().String())
} else {
logging.Info("=Server= Could not read from %s: %s", conn.RemoteAddr().String(), err)
}
return
}
logging.Info("=Server= Received %s", msg)
line := irc.ParseLine(string(msg))
if line == nil {
logging.Error("=Server= Could not parse received line")
continue
}
err = s.handleLine(bufConn, line)
if err != nil {
logging.Info("=Server= Closing connection: %s", err)
return
}
bufConn.Flush()
}
}
func (s *testServer) SendMsg(msg string) error {
if s.Client == nil {
return errors.New("Cannot write without client connected")
}
bufConn := bufio.NewWriter(s.Client)
logging.Info("=Server= sending to client: %s", msg)
_, err := bufConn.WriteString(msg)
bufConn.Flush()
return err
}
func (s *testServer) SetCloseEarly(h closeEarlyHandler) {
s.closeEarlyMu.Lock()
defer s.closeEarlyMu.Unlock()
s.closeEarlyHandler = h
}
func (s *testServer) handleCloseEarly(conn net.Conn) bool {
s.closeEarlyMu.Lock()
defer s.closeEarlyMu.Unlock()
if s.closeEarlyHandler == nil {
return false
}
logging.Info("=Server= Closing connection early")
conn.Close()
s.closeEarlyHandler()
return true
}
func (s *testServer) Serve() {
defer s.ServingWaitGroup.Done()
for {
conn, err := s.Listener.Accept()
if err != nil {
logging.Info("=Server= Stopped accepting new connections")
return
}
logging.Info("=Server= New client connected from %s", conn.RemoteAddr().String())
if s.handleCloseEarly(conn) {
continue
}
s.Client = conn
s.ConnectionsWaitGroup.Add(1)
s.handleConnection(conn)
}
}
func (s *testServer) Stop() {
s.Listener.Close()
s.ServingWaitGroup.Wait()
s.ConnectionsWaitGroup.Wait()
}
func makeTestServer(t *testing.T) (*testServer, int) {
server := new(testServer)
server.Log = make([]string, 0)
server.setDefaultHandlers()
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("=Server= Could not resolve tcp addr: %s", err)
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
t.Fatalf("=Server= Could not create listener: %s", err)
}
addr = listener.Addr().(*net.TCPAddr)
logging.Info("=Server= Test server listening on %s", addr.String())
server.Listener = listener
server.ServingWaitGroup.Add(1)
go func() {
server.Serve()
}()
addr = listener.Addr().(*net.TCPAddr)
return server, addr.Port
}
alertmanager-irc-relay-0.5.1/irc_test.go 0000664 0000000 0000000 00000035355 14754146416 0020236 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"bufio"
"context"
"fmt"
"reflect"
"strings"
"sync"
"testing"
"time"
irc "github.com/fluffle/goirc/client"
"github.com/google/alertmanager-irc-relay/logging"
)
func makeTestIRCConfig(IRCPort int) *Config {
return &Config{
IRCNick: "foo",
IRCNickPass: "",
IRCHost: "127.0.0.1",
IRCPort: IRCPort,
IRCUseSSL: false,
IRCChannels: []IRCChannel{
IRCChannel{Name: "#foo"},
},
UsePrivmsg: false,
NickservIdentifyPatterns: []string{
"identify yourself ktnxbye",
},
NickservName: "NickServ",
ChanservName: "ChanServ",
}
}
func makeTestNotifier(t *testing.T, config *Config) (*IRCNotifier, chan AlertMsg, context.Context, context.CancelFunc, *sync.WaitGroup) {
fakeDelayerMaker := &FakeDelayerMaker{}
fakeTime := &FakeTime{
afterChan: make(chan time.Time, 1),
}
alertMsgs := make(chan AlertMsg)
ctx, cancel := context.WithCancel(context.Background())
stopWg := sync.WaitGroup{}
stopWg.Add(1)
notifier, err := NewIRCNotifier(config, alertMsgs, fakeDelayerMaker, fakeTime)
if err != nil {
t.Fatal(fmt.Sprintf("Could not create IRC notifier: %s", err))
}
notifier.Client.Config().Flood = true
return notifier, alertMsgs, ctx, cancel, &stopWg
}
func TestServerPassword(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
config.IRCHostPass = "hostsecret"
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
var testStep sync.WaitGroup
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return nil
}
server.SetHandler("JOIN", joinHandler)
testStep.Add(1)
go notifier.Run(ctx, stopWg)
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"PASS hostsecret",
"NICK foo",
"USER foo 12 * :",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Did not send IRC server password. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestSendAlertOnPreJoinedChannel(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
notifier, alertMsgs, ctx, cancel, stopWg := makeTestNotifier(t, config)
var testStep sync.WaitGroup
testChannel := "#foo"
testMessage := "test message"
// Send the alert after configured channels have joined, to ensure we
// check for no re-join attempt.
joinedHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
if line.Args[0] == testChannel {
testStep.Done()
}
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinedHandler)
testStep.Add(1)
go notifier.Run(ctx, stopWg)
testStep.Wait()
server.SetHandler("JOIN", hJOIN)
noticeHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return nil
}
server.SetHandler("NOTICE", noticeHandler)
testStep.Add(1)
alertMsgs <- AlertMsg{Channel: testChannel, Alert: testMessage}
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"NICK foo",
"USER foo 12 * :",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
"NOTICE #foo :test message",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestUsePrivmsgToSendAlertOnPreJoinedChannel(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
config.UsePrivmsg = true
notifier, alertMsgs, ctx, cancel, stopWg := makeTestNotifier(t, config)
var testStep sync.WaitGroup
testChannel := "#foo"
testMessage := "test message"
// Send the alert after configured channels have joined, to ensure we
// check for no re-join attempt.
joinedHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
if line.Args[0] == testChannel {
testStep.Done()
}
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinedHandler)
testStep.Add(1)
go notifier.Run(ctx, stopWg)
testStep.Wait()
server.SetHandler("JOIN", hJOIN)
privmsgHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return nil
}
server.SetHandler("PRIVMSG", privmsgHandler)
testStep.Add(1)
alertMsgs <- AlertMsg{Channel: testChannel, Alert: testMessage}
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"NICK foo",
"USER foo 12 * :",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
"PRIVMSG #foo :test message",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestSendAlertAndJoinChannel(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
notifier, alertMsgs, ctx, cancel, stopWg := makeTestNotifier(t, config)
var testStep sync.WaitGroup
testChannel := "#foobar"
testMessage := "test message"
// Send the alert after configured channels have joined, to ensure log
// ordering.
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinHandler)
testStep.Add(1)
go notifier.Run(ctx, stopWg)
testStep.Wait()
server.SetHandler("JOIN", hJOIN)
noticeHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return nil
}
server.SetHandler("NOTICE", noticeHandler)
testStep.Add(1)
alertMsgs <- AlertMsg{Channel: testChannel, Alert: testMessage}
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"NICK foo",
"USER foo 12 * :",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
// #foobar joined before sending message
"PRIVMSG ChanServ :UNBAN #foobar",
"JOIN #foobar",
"NOTICE #foobar :test message",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestSendAlertDisconnected(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
notifier, alertMsgs, ctx, cancel, stopWg := makeTestNotifier(t, config)
var testStep, holdUserStep sync.WaitGroup
testChannel := "#foo"
disconnectedTestMessage := "disconnected test message"
connectedTestMessage := "connected test message"
// First send an alert while the session is not established.
testStep.Add(1)
holdUserStep.Add(1)
holdUser := func(conn *bufio.ReadWriter, line *irc.Line) error {
logging.Info("=Server= Wait before completing session")
testStep.Wait()
logging.Info("=Server= Completing session")
holdUserStep.Done()
return hUSER(conn, line)
}
server.SetHandler("USER", holdUser)
go notifier.Run(ctx, stopWg)
// Alert channels is not consumed while disconnected
select {
case alertMsgs <- AlertMsg{Channel: testChannel, Alert: disconnectedTestMessage}:
t.Error("Alert consumed while disconnected")
default:
}
testStep.Done()
holdUserStep.Wait()
// Make sure session is established by checking that pre-joined
// channel is there.
testStep.Add(1)
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinHandler)
testStep.Wait()
// Now send and wait until a notice has been received.
testStep.Add(1)
noticeHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return nil
}
server.SetHandler("NOTICE", noticeHandler)
alertMsgs <- AlertMsg{Channel: testChannel, Alert: connectedTestMessage}
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"NICK foo",
"USER foo 12 * :",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
// Only message sent while being connected is received.
"NOTICE #foo :connected test message",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestReconnect(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
var testStep sync.WaitGroup
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinHandler)
testStep.Add(1)
go notifier.Run(ctx, stopWg)
// Wait until the pre-joined channel is seen.
testStep.Wait()
// Simulate disconnection.
testStep.Add(1)
server.Client.Close()
// Wait again until the pre-joined channel is seen.
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
// Commands from first connection
"NICK foo",
"USER foo 12 * :",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
// Commands from reconnection
"NICK foo",
"USER foo 12 * :",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Reconnection did not happen correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestConnectErrorRetry(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
// Attempt SSL handshake. The server does not support it, resulting in
// a connection error.
config.IRCUseSSL = true
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
// Pilot reconnect attempts via backoff delay to prevent race
// conditions in the test while we change the components behavior on
// the fly.
delayer := notifier.BackoffCounter.(*FakeDelayer)
delayer.DelayOnChan = true
var testStep, joinStep sync.WaitGroup
testStep.Add(1)
earlyHandler := func() {
testStep.Done()
}
server.SetCloseEarly(earlyHandler)
go notifier.Run(ctx, stopWg)
delayer.StopDelay <- true
testStep.Wait()
// We have caused a connection failure, now check for a reconnection
notifier.Client.Config().SSL = false
joinStep.Add(1)
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
joinStep.Done()
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinHandler)
server.SetCloseEarly(nil)
delayer.StopDelay <- true
joinStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"NICK foo",
"USER foo 12 * :",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Reconnection did not happen correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestIdentify(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
config.IRCNickPass = "nickpassword"
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
notifier.NickservDelayWait = 0 * time.Second
var testStep sync.WaitGroup
// Trigger NickServ identify request when we see the NICK command
// Note: We also test formatting cleanup with this message
nickHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
var err error
_, err = conn.WriteString(":NickServ!NickServ@services. NOTICE airtest :This nickname is registered. Please choose a different nickname, or \002identify yourself\002 ktnxbye.\n")
return err
}
server.SetHandler("NICK", nickHandler)
// Wait until the pre-joined channel is seen (joining happens
// after identification).
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinHandler)
testStep.Add(1)
go notifier.Run(ctx, stopWg)
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"NICK foo",
"USER foo 12 * :",
"PRIVMSG NickServ :IDENTIFY nickpassword",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Identification did not happen correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestGhost(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
config.IRCNickPass = "nickpassword"
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
notifier.NickservDelayWait = 0 * time.Second
var testStep sync.WaitGroup
// Trigger 433 for first nick when we see the USER command
userHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
var err error
if line.Args[0] == "foo" {
_, err = conn.WriteString(":example.com 433 * foo :nick in use\n")
}
return err
}
server.SetHandler("USER", userHandler)
// Trigger 001 when we see NICK foo^
nickHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
var err error
if line.Args[0] == "foo^" {
_, err = conn.WriteString(":example.com 001 foo^ :Welcome\n")
}
return err
}
server.SetHandler("NICK", nickHandler)
// Wait until the pre-joined channel is seen (joining happens
// after ghosting).
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
testStep.Done()
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinHandler)
testStep.Add(1)
go notifier.Run(ctx, stopWg)
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"NICK foo",
"USER foo 12 * :",
"NICK foo^",
"PRIVMSG NickServ :GHOST foo nickpassword",
"NICK foo",
"PRIVMSG ChanServ :UNBAN #foo",
"JOIN #foo",
"QUIT :see ya",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Ghosting did not happen correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
func TestStopRunningWhenHalfConnected(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
var testStep sync.WaitGroup
// Send a StopRunning request while the client is connected but the
// session is not up
testStep.Add(1)
holdUser := func(conn *bufio.ReadWriter, line *irc.Line) error {
logging.Info("=Server= NOT completing session")
testStep.Done()
return nil
}
server.SetHandler("USER", holdUser)
go notifier.Run(ctx, stopWg)
testStep.Wait()
cancel()
stopWg.Wait()
server.Stop()
expectedCommands := []string{
"NICK foo",
"USER foo 12 * :",
}
if !reflect.DeepEqual(expectedCommands, server.Log) {
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
}
}
alertmanager-irc-relay-0.5.1/logging/ 0000775 0000000 0000000 00000000000 14754146416 0017506 5 ustar 00root root 0000000 0000000 alertmanager-irc-relay-0.5.1/logging/logging.go 0000664 0000000 0000000 00000003700 14754146416 0021463 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google LLC
//
// 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
//
// https://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 logging
import (
"flag"
"fmt"
"log"
"os"
goirc_logging "github.com/fluffle/goirc/logging"
)
const (
loggingCallDepth = 3
)
type Logger interface {
Debug(format string, args ...interface{})
Info(format string, args ...interface{})
Warn(format string, args ...interface{})
Error(format string, args ...interface{})
}
var logger Logger
type stdOutLogger struct {
out *log.Logger
}
var debugFlag = flag.Bool("debug", false, "Enable debug logging.")
func (l stdOutLogger) Debug(f string, a ...interface{}) {
if *debugFlag {
l.out.Output(loggingCallDepth, fmt.Sprintf("DEBUG "+f, a...))
}
}
func (l stdOutLogger) Info(f string, a ...interface{}) {
l.out.Output(loggingCallDepth, fmt.Sprintf("INFO "+f, a...))
}
func (l stdOutLogger) Warn(f string, a ...interface{}) {
l.out.Output(loggingCallDepth, fmt.Sprintf("WARN "+f, a...))
}
func (l stdOutLogger) Error(f string, a ...interface{}) {
l.out.Output(loggingCallDepth, fmt.Sprintf("ERROR "+f, a...))
}
func init() {
logger = stdOutLogger{
out: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds|log.Lshortfile),
}
goirc_logging.SetLogger(logger)
}
func Debug(f string, a ...interface{}) { logger.Debug(f, a...) }
func Info(f string, a ...interface{}) { logger.Info(f, a...) }
func Warn(f string, a ...interface{}) { logger.Warn(f, a...) }
func Error(f string, a ...interface{}) { logger.Error(f, a...) }
alertmanager-irc-relay-0.5.1/main.go 0000664 0000000 0000000 00000002714 14754146416 0017337 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
import (
"context"
"flag"
"sync"
"syscall"
"github.com/google/alertmanager-irc-relay/logging"
)
func main() {
configFile := flag.String("config", "", "Config file path.")
flag.Parse()
ctx, _ := WithSignal(context.Background(), syscall.SIGINT, syscall.SIGTERM)
stopWg := sync.WaitGroup{}
config, err := LoadConfig(*configFile)
if err != nil {
logging.Error("Could not load config: %s", err)
return
}
alertMsgs := make(chan AlertMsg, config.AlertBufferSize)
stopWg.Add(1)
ircNotifier, err := NewIRCNotifier(config, alertMsgs, &BackoffMaker{}, &RealTime{})
if err != nil {
logging.Error("Could not create IRC notifier: %s", err)
return
}
go ircNotifier.Run(ctx, &stopWg)
httpServer, err := NewHTTPServer(config, alertMsgs)
if err != nil {
logging.Error("Could not create HTTP server: %s", err)
return
}
go httpServer.Run()
stopWg.Wait()
}
alertmanager-irc-relay-0.5.1/reconciler.go 0000664 0000000 0000000 00000015315 14754146416 0020541 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google LLC
//
// 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
//
// https://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 main
import (
"context"
"sync"
"time"
irc "github.com/fluffle/goirc/client"
"github.com/google/alertmanager-irc-relay/logging"
)
const (
ircJoinWaitSecs = 10
ircJoinMaxBackoffSecs = 300
ircJoinBackoffResetSecs = 1800
)
type channelState struct {
channel IRCChannel
chanservName string
client *irc.Conn
delayer Delayer
timeTeller TimeTeller
joinDone chan struct{} // joined when channel is closed
joined bool
joinUnsetSignal chan bool
mu sync.Mutex
}
func newChannelState(channel *IRCChannel, client *irc.Conn, delayerMaker DelayerMaker, timeTeller TimeTeller, chanservName string) *channelState {
delayer := delayerMaker.NewDelayer(ircJoinMaxBackoffSecs, ircJoinBackoffResetSecs, time.Second)
return &channelState{
channel: *channel,
client: client,
delayer: delayer,
timeTeller: timeTeller,
joinDone: make(chan struct{}),
joined: false,
joinUnsetSignal: make(chan bool),
chanservName: chanservName,
}
}
func (c *channelState) JoinDone() <-chan struct{} {
c.mu.Lock()
defer c.mu.Unlock()
return c.joinDone
}
func (c *channelState) SetJoined() {
c.mu.Lock()
defer c.mu.Unlock()
if c.joined == true {
logging.Warn("Not setting JOIN state on channel %s: already set", c.channel.Name)
return
}
logging.Info("Setting JOIN state on channel %s", c.channel.Name)
c.joined = true
close(c.joinDone)
}
func (c *channelState) UnsetJoined() {
c.mu.Lock()
defer c.mu.Unlock()
if c.joined == false {
logging.Warn("Not removing JOIN state on channel %s: already not set", c.channel.Name)
return
}
logging.Info("Removing JOIN state on channel %s", c.channel.Name)
c.joined = false
c.joinDone = make(chan struct{})
// eventually poke monitor routine
select {
case c.joinUnsetSignal <- true:
default:
}
}
func (c *channelState) join(ctx context.Context) {
logging.Info("Channel %s monitor: waiting to join", c.channel.Name)
if ok := c.delayer.DelayContext(ctx); !ok {
return
}
// Try to unban ourselves, just in case
c.client.Privmsgf(c.chanservName, "UNBAN %s", c.channel.Name)
c.client.Join(c.channel.Name, c.channel.Password)
logging.Info("Channel %s monitor: join request sent", c.channel.Name)
select {
case <-c.JoinDone():
logging.Info("Channel %s monitor: join succeeded", c.channel.Name)
case <-c.timeTeller.After(ircJoinWaitSecs * time.Second):
logging.Warn("Channel %s monitor: could not join after %d seconds, will retry", c.channel.Name, ircJoinWaitSecs)
case <-ctx.Done():
logging.Info("Channel %s monitor: context canceled while waiting for join", c.channel.Name)
}
}
func (c *channelState) monitorJoinUnset(ctx context.Context) {
select {
case <-c.joinUnsetSignal:
logging.Info("Channel %s monitor: channel no longer joined", c.channel.Name)
case <-ctx.Done():
logging.Info("Channel %s monitor: context canceled while monitoring", c.channel.Name)
}
}
func (c *channelState) Monitor(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
joined := func() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.joined
}
for ctx.Err() != context.Canceled {
if !joined() {
c.join(ctx)
} else {
c.monitorJoinUnset(ctx)
}
}
}
type ChannelReconciler struct {
preJoinChannels []IRCChannel
client *irc.Conn
delayerMaker DelayerMaker
timeTeller TimeTeller
channels map[string]*channelState
chanservName string
stopCtx context.Context
stopCtxCancel context.CancelFunc
stopWg sync.WaitGroup
mu sync.Mutex
}
func NewChannelReconciler(config *Config, client *irc.Conn, delayerMaker DelayerMaker, timeTeller TimeTeller) *ChannelReconciler {
reconciler := &ChannelReconciler{
preJoinChannels: config.IRCChannels,
client: client,
delayerMaker: delayerMaker,
timeTeller: timeTeller,
channels: make(map[string]*channelState),
chanservName: config.ChanservName,
}
reconciler.registerHandlers()
return reconciler
}
func (r *ChannelReconciler) registerHandlers() {
r.client.HandleFunc(irc.JOIN,
func(_ *irc.Conn, line *irc.Line) {
r.HandleJoin(line.Nick, line.Args[0])
})
r.client.HandleFunc(irc.KICK,
func(_ *irc.Conn, line *irc.Line) {
r.HandleKick(line.Args[1], line.Args[0])
})
}
func (r *ChannelReconciler) HandleJoin(nick string, channel string) {
r.mu.Lock()
defer r.mu.Unlock()
if nick != r.client.Me().Nick {
// received join info for somebody else
return
}
logging.Info("Received JOIN confirmation for channel %s", channel)
c, ok := r.channels[channel]
if !ok {
logging.Warn("Not processing JOIN for channel %s: unknown channel", channel)
return
}
c.SetJoined()
}
func (r *ChannelReconciler) HandleKick(nick string, channel string) {
r.mu.Lock()
defer r.mu.Unlock()
if nick != r.client.Me().Nick {
// received kick info for somebody else
return
}
logging.Info("Received KICK for channel %s", channel)
c, ok := r.channels[channel]
if !ok {
logging.Warn("Not processing KICK for channel %s: unknown channel", channel)
return
}
c.UnsetJoined()
}
func (r *ChannelReconciler) unsafeAddChannel(channel *IRCChannel) *channelState {
c := newChannelState(channel, r.client, r.delayerMaker, r.timeTeller, r.chanservName)
r.stopWg.Add(1)
go c.Monitor(r.stopCtx, &r.stopWg)
r.channels[channel.Name] = c
return c
}
func (r *ChannelReconciler) JoinChannel(channel string) (bool, <-chan struct{}) {
r.mu.Lock()
defer r.mu.Unlock()
c, ok := r.channels[channel]
if !ok {
logging.Info("Request to JOIN new channel %s", channel)
c = r.unsafeAddChannel(&IRCChannel{Name: channel})
}
select {
case <-c.JoinDone():
return true, nil
default:
return false, c.JoinDone()
}
}
func (r *ChannelReconciler) unsafeStop() {
if r.stopCtxCancel == nil {
// calling stop before first start, ignoring
return
}
r.stopCtxCancel()
r.stopWg.Wait()
r.channels = make(map[string]*channelState)
}
func (r *ChannelReconciler) Stop() {
r.mu.Lock()
defer r.mu.Unlock()
r.unsafeStop()
}
func (r *ChannelReconciler) Start(ctx context.Context) {
r.mu.Lock()
defer r.mu.Unlock()
r.unsafeStop()
r.stopCtx, r.stopCtxCancel = context.WithCancel(ctx)
for _, channel := range r.preJoinChannels {
r.unsafeAddChannel(&channel)
}
}
alertmanager-irc-relay-0.5.1/reconciler_test.go 0000664 0000000 0000000 00000007735 14754146416 0021607 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google LLC
//
// 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
//
// https://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 main
import (
"bufio"
"context"
"reflect"
"sort"
"sync"
"testing"
"time"
irc "github.com/fluffle/goirc/client"
)
func makeTestReconciler(config *Config) (*ChannelReconciler, chan bool, chan bool, *FakeTime) {
sessionUp := make(chan bool)
sessionDown := make(chan bool)
client := irc.Client(makeGOIRCConfig(config))
client.Config().Flood = true
client.HandleFunc(irc.CONNECTED,
func(*irc.Conn, *irc.Line) {
sessionUp <- true
})
client.HandleFunc(irc.DISCONNECTED,
func(*irc.Conn, *irc.Line) {
sessionDown <- false
})
fakeDelayerMaker := &FakeDelayerMaker{}
fakeTime := &FakeTime{
afterChan: make(chan time.Time, 1),
}
reconciler := NewChannelReconciler(config, client, fakeDelayerMaker, fakeTime)
return reconciler, sessionUp, sessionDown, fakeTime
}
func TestPreJoinChannels(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
config.IRCChannels = []IRCChannel{
IRCChannel{Name: "#foo"},
IRCChannel{Name: "#bar"},
IRCChannel{Name: "#baz"},
}
reconciler, sessionUp, sessionDown, _ := makeTestReconciler(config)
var testStep sync.WaitGroup
joinedChannels := []string{}
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
joinedChannels = append(joinedChannels, line.Args[0])
if len(joinedChannels) == 3 {
testStep.Done()
}
return hJOIN(conn, line)
}
server.SetHandler("JOIN", joinHandler)
testStep.Add(1)
reconciler.client.Connect()
<-sessionUp
reconciler.Start(context.Background())
testStep.Wait()
reconciler.client.Quit("see ya")
<-sessionDown
reconciler.Stop()
server.Stop()
expectedJoinedChannels := []string{"#bar", "#baz", "#foo"}
sort.Strings(joinedChannels)
if !reflect.DeepEqual(expectedJoinedChannels, joinedChannels) {
t.Error("Did not pre-join channels")
}
}
func TestKeepJoining(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
reconciler, sessionUp, sessionDown, fakeTime := makeTestReconciler(config)
var testStep sync.WaitGroup
var joinedCounter int
// Confirm join only after a few attempts
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
joinedCounter++
if joinedCounter == 3 {
testStep.Done()
return hJOIN(conn, line)
} else {
fakeTime.afterChan <- time.Now()
}
return nil
}
server.SetHandler("JOIN", joinHandler)
testStep.Add(1)
reconciler.client.Connect()
<-sessionUp
reconciler.Start(context.Background())
testStep.Wait()
reconciler.client.Quit("see ya")
<-sessionDown
reconciler.Stop()
server.Stop()
expectedJoinedCounter := 3
if !reflect.DeepEqual(expectedJoinedCounter, joinedCounter) {
t.Error("Did not keep joining")
}
}
func TestKickRejoin(t *testing.T) {
server, port := makeTestServer(t)
config := makeTestIRCConfig(port)
reconciler, sessionUp, sessionDown, _ := makeTestReconciler(config)
var testStep sync.WaitGroup
// Wait for channel to be joined
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
hJOIN(conn, line)
testStep.Done()
return nil
}
server.SetHandler("JOIN", joinHandler)
testStep.Add(1)
reconciler.client.Connect()
<-sessionUp
reconciler.Start(context.Background())
testStep.Wait()
// Kick and wait for channel to be joined again
testStep.Add(1)
server.SendMsg(":test!~test@example.com KICK #foo foo :Bye!\n")
testStep.Wait()
reconciler.client.Quit("see ya")
<-sessionDown
reconciler.Stop()
server.Stop()
}
alertmanager-irc-relay-0.5.1/testdata.go 0000664 0000000 0000000 00000005116 14754146416 0020223 0 ustar 00root root 0000000 0000000 // Copyright 2018 Google LLC
//
// 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
//
// https://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 main
const (
testdataSimpleAlertJson = `
{
"status": "resolved",
"receiver": "example_receiver",
"groupLabels": {
"alertname": "airDown",
"service": "prometheus"
},
"commonLabels": {
"alertname": "airDown",
"job": "air",
"service": "prometheus",
"severity": "ticket",
"zone": "global"
},
"commonAnnotations": {},
"externalURL": "https://prometheus.example.com/alertmanager",
"alerts": [
{
"annotations": {
"SUMMARY": "service /prometheus air down on instance1",
"DESCRIPTION": "service /prometheus has irc gateway down on instance1"
},
"endsAt": "2017-05-15T13:50:37.835Z",
"generatorURL": "https://prometheus.example.com/prometheus/...",
"fingerprint": "66214a361160fb6f",
"labels": {
"alertname": "airDown",
"instance": "instance1:3456",
"job": "air",
"service": "prometheus",
"severity": "ticket",
"zone": "global"
},
"startsAt": "2017-05-15T13:49:37.834Z",
"status": "resolved"
},
{
"annotations": {
"SUMMARY": "service /prometheus air down on instance2",
"DESCRIPTION": "service /prometheus has irc gateway down on instance2"
},
"endsAt": "2017-05-15T11:48:37.834Z",
"generatorURL": "https://prometheus.example.com/prometheus/...",
"fingerprint": "25a874c99325d1ce",
"labels": {
"alertname": "airDown",
"instance": "instance2:7890",
"job": "air",
"service": "prometheus",
"severity": "ticket",
"zone": "global"
},
"startsAt": "2017-05-15T11:47:37.834Z",
"status": "resolved"
}
]
}
`
testdataBogusAlertJson = `{"this is not": "a valid alert",}`
)
alertmanager-irc-relay-0.5.1/time.go 0000664 0000000 0000000 00000001663 14754146416 0017353 0 ustar 00root root 0000000 0000000 // Copyright 2021 Google LLC
//
// 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
//
// https://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 main
import (
"time"
)
// TimeTeller interface allows injection of fake time during testing
type TimeTeller interface {
Now() time.Time
After(time.Duration) <-chan time.Time
}
type RealTime struct{}
func (r *RealTime) Now() time.Time {
return time.Now()
}
func (r *RealTime) After(d time.Duration) <-chan time.Time {
return time.After(d)
}