├── .gitignore ├── .dockerignore ├── docs ├── dog.jpg ├── dog.xcf ├── design.jpg ├── getting_started.md └── design.md ├── example ├── namespace.yml ├── kustomization.yaml ├── alertdog.yml ├── alertdog-config.yml ├── prometheus-a.yml ├── prometheus-b.yml └── alertmanager.yml ├── go.mod ├── .github └── workflows │ ├── ci.yml │ ├── pr.yml │ └── release.yml ├── main.go ├── pkg ├── alertmanager │ ├── alert.go │ ├── alertmanager.go │ └── alertmanager_test.go └── alertdog │ ├── prometheus.go │ ├── alertdog.go │ └── alertdog_test.go ├── Dockerfile ├── README.md ├── LICENSE └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | config.yml 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | docs/* 2 | example/* 3 | config.* 4 | README.md 5 | -------------------------------------------------------------------------------- /docs/dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/errm/alertdog/HEAD/docs/dog.jpg -------------------------------------------------------------------------------- /docs/dog.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/errm/alertdog/HEAD/docs/dog.xcf -------------------------------------------------------------------------------- /docs/design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/errm/alertdog/HEAD/docs/design.jpg -------------------------------------------------------------------------------- /example/namespace.yml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: alertdog-example 5 | -------------------------------------------------------------------------------- /example/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: alertdog-example 4 | resources: 5 | - namespace.yml 6 | - prometheus-a.yml 7 | - prometheus-b.yml 8 | - alertmanager.yml 9 | - alertdog.yml 10 | secretGenerator: 11 | - name: alertdog-config 12 | files: 13 | - alertdog-config.yml 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/errm/alertdog 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/PagerDuty/go-pagerduty v1.3.0 7 | github.com/creasty/defaults v1.5.1 // indirect 8 | github.com/gin-gonic/gin v1.6.3 // indirect 9 | github.com/prometheus/alertmanager v0.21.0 10 | github.com/prometheus/client_golang v1.6.0 11 | github.com/stretchr/testify v1.5.1 12 | go.uber.org/atomic v1.5.0 13 | gopkg.in/yaml.v2 v2.3.0 14 | ) 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - 15 | uses: actions/checkout@v2 16 | - 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: '^1.16' 20 | - 21 | run: go test -v ./... 22 | lint: 23 | name: lint 24 | runs-on: ubuntu-latest 25 | steps: 26 | - 27 | uses: actions/checkout@v2 28 | - 29 | name: golangci-lint 30 | uses: golangci/golangci-lint-action@v2 31 | with: 32 | version: v1.37.1 33 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | 9 | "gopkg.in/yaml.v2" 10 | 11 | "github.com/errm/alertdog/pkg/alertdog" 12 | ) 13 | 14 | func main() { 15 | a := readConfig() 16 | a.Setup() 17 | go a.CheckLoop() 18 | http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { 19 | w.WriteHeader(http.StatusOK) 20 | }) 21 | http.Handle("/webhook", a) 22 | log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", a.Port), nil)) 23 | } 24 | 25 | func readConfig() *alertdog.Alertdog { 26 | var a *alertdog.Alertdog 27 | configFile, err := ioutil.ReadFile("config.yml") 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | err = yaml.Unmarshal(configFile, &a) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | return a 36 | } 37 | -------------------------------------------------------------------------------- /pkg/alertmanager/alert.go: -------------------------------------------------------------------------------- 1 | package alertmanager 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/prometheus/alertmanager/client" 7 | ) 8 | 9 | type Alert struct { 10 | Name string 11 | Labels map[string]string 12 | Annotations map[string]string 13 | } 14 | 15 | func (a Alert) clientAlert() client.Alert { 16 | labels := toLabelSet(a.Labels) 17 | labels[client.LabelName("alertname")] = client.LabelValue(a.Name) 18 | return client.Alert{ 19 | Labels: labels, 20 | Annotations: toLabelSet(a.Annotations), 21 | StartsAt: time.Now(), 22 | } 23 | } 24 | 25 | func toLabelSet(labels map[string]string) client.LabelSet { 26 | labelSet := make(client.LabelSet, len(labels)) 27 | for name, value := range labels { 28 | labelSet[client.LabelName(name)] = client.LabelValue(value) 29 | } 30 | return labelSet 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Docker PR 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | docker: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - 11 | uses: actions/checkout@v2 12 | - 13 | name: Inject slug/short variables 14 | uses: rlespinasse/github-slug-action@v3.x 15 | - 16 | name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v1 18 | - 19 | name: Login to DockerHub 20 | uses: docker/login-action@v1 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | - 25 | name: Build and push 26 | id: docker_build 27 | uses: docker/build-push-action@v2 28 | with: 29 | push: true 30 | tags: ${{ secrets.DOCKERHUB_PR_REPO }}:${{ env.GITHUB_HEAD_REF_SLUG }} 31 | platforms: linux/amd64,linux/arm64 32 | -------------------------------------------------------------------------------- /example/alertdog.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: alertdog 5 | labels: 6 | app: alertdog 7 | spec: 8 | selector: 9 | app: alertdog 10 | ports: 11 | - port: 9796 12 | name: webhook 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: alertdog 18 | labels: 19 | app: alertdog 20 | spec: 21 | replicas: 1 22 | selector: 23 | matchLabels: 24 | app: alertdog 25 | template: 26 | metadata: 27 | labels: 28 | app: alertdog 29 | spec: 30 | containers: 31 | - name: alertdog 32 | image: quay.io/errm/alertdog:0.0.2 33 | ports: 34 | - containerPort: 9767 35 | volumeMounts: 36 | - name: config-volume 37 | mountPath: /config.yml 38 | subPath: config.yml 39 | volumes: 40 | - name: config-volume 41 | secret: 42 | secretName: alertdog-config 43 | items: 44 | - key: alertdog-config.yml 45 | path: config.yml 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${BUILDPLATFORM} golang:1.16-alpine AS build 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | RUN apk add --no-cache ca-certificates && update-ca-certificates 7 | 8 | ENV USER=alertdog 9 | ENV UID=10001 10 | 11 | RUN adduser \ 12 | --disabled-password \ 13 | --gecos "" \ 14 | --home "/nonexistent" \ 15 | --shell "/sbin/nologin" \ 16 | --no-create-home \ 17 | --uid "${UID}" \ 18 | "${USER}" 19 | 20 | WORKDIR /src 21 | ENV CGO_ENABLED=0 22 | COPY go.* ./ 23 | RUN --mount=type=cache,target=/go/pkg/mod \ 24 | go mod download && go mod verify 25 | 26 | RUN --mount=target=. \ 27 | --mount=type=cache,target=/go/pkg/mod \ 28 | --mount=type=cache,target=/root/.cache/go-build \ 29 | GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-w -s" -o /out/alertdog . 30 | 31 | FROM scratch 32 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 33 | COPY --from=build /etc/passwd /etc/passwd 34 | COPY --from=build /etc/group /etc/group 35 | 36 | COPY --from=build /out/alertdog / 37 | 38 | USER alertdog:alertdog 39 | 40 | ENTRYPOINT ["/alertdog"] 41 | -------------------------------------------------------------------------------- /example/alertdog-config.yml: -------------------------------------------------------------------------------- 1 | alertmanager_endpoints: 2 | - http://alertmanager-0:9093 3 | - http://alertmanager-1:9093 4 | pager_duty_key: PAGER_DUTY_KEY 5 | pagerduty_runbook_url: https://example.org/alertmanager_down_runbook 6 | expected: 7 | - match_labels: 8 | alertname: Watchdog 9 | owner: team-a 10 | alert: 11 | name: PrometheusAlertFailure 12 | labels: 13 | severity: critical 14 | owner: team-a 15 | service: monitoring 16 | component: prometheus 17 | annotations: 18 | runbook: https://example.org/prometheus-alert-failure 19 | description: | 20 | This alert fires when the "Watchdog" alert fails to be received 21 | from a prometheus instance. 22 | It could indicate that the prometheus instance is not running, or 23 | that there is a configuration issue with prometheus or alertmanager. 24 | - match_labels: 25 | alertname: Watchdog 26 | owner: team-b 27 | alert: 28 | name: PrometheusAlertFailure 29 | labels: 30 | severity: critical 31 | owner: team-b 32 | service: monitoring 33 | component: prometheus 34 | annotations: 35 | runbook: https://example.org/prometheus-alert-failure 36 | description: | 37 | This alert fires when the "Watchdog" alert fails to be received 38 | from a prometheus instance. 39 | It could indicate that the prometheus instance is not running, or 40 | that there is a configuration issue with prometheus or alertmanager. 41 | -------------------------------------------------------------------------------- /pkg/alertdog/prometheus.go: -------------------------------------------------------------------------------- 1 | package alertdog 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/errm/alertdog/pkg/alertmanager" 8 | "github.com/prometheus/alertmanager/template" 9 | ) 10 | 11 | type AlertAction int 12 | 13 | const ( 14 | ActionNone AlertAction = iota 15 | ActionAlert 16 | ActionResolve 17 | ) 18 | 19 | type Prometheus struct { 20 | MatchLabels map[string]string `yaml:"match_labels"` 21 | Expiry time.Duration 22 | Alert alertmanager.Alert 23 | checkedIn time.Time 24 | count uint 25 | mu sync.RWMutex 26 | } 27 | 28 | func (p *Prometheus) UnmarshalYAML(unmarshal func(interface{}) error) error { 29 | defaultExpiry, _ := time.ParseDuration("4m") 30 | p.Expiry = defaultExpiry 31 | type plain Prometheus 32 | return unmarshal((*plain)(p)) 33 | } 34 | 35 | func (p *Prometheus) CheckIn(alert template.Alert) AlertAction { 36 | if p.match(alert.Labels) { 37 | p.mu.Lock() 38 | defer p.mu.Unlock() 39 | if alert.Status == "firing" { 40 | p.checkedIn = time.Now() 41 | p.count += 1 42 | // Debounce during state change, wait for 2 alerts before resolving 43 | if p.count == 2 { 44 | return ActionResolve 45 | } 46 | } else { 47 | p.count = 0 48 | return ActionAlert 49 | } 50 | } 51 | return ActionNone 52 | } 53 | 54 | func (p *Prometheus) Check() AlertAction { 55 | if p.Expired() { 56 | p.mu.Lock() 57 | defer p.mu.Unlock() 58 | p.count = 0 59 | return ActionAlert 60 | } 61 | return ActionNone 62 | } 63 | 64 | func (p *Prometheus) match(labels map[string]string) bool { 65 | for key, value := range p.MatchLabels { 66 | if labels[key] != value { 67 | return false 68 | } 69 | } 70 | return true 71 | } 72 | 73 | func (p *Prometheus) Expired() bool { 74 | p.mu.RLock() 75 | defer p.mu.RUnlock() 76 | return time.Now().After(p.checkedIn.Add(p.Expiry)) 77 | } 78 | -------------------------------------------------------------------------------- /pkg/alertmanager/alertmanager.go: -------------------------------------------------------------------------------- 1 | package alertmanager 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "sync" 8 | "time" 9 | 10 | "github.com/prometheus/alertmanager/client" 11 | "github.com/prometheus/client_golang/api" 12 | "go.uber.org/atomic" 13 | ) 14 | 15 | type Alertmanager struct { 16 | Endpoints []string 17 | Expiry time.Duration 18 | } 19 | 20 | func (a Alertmanager) Alert(alert Alert) error { 21 | clientAlert := alert.clientAlert() 22 | now := time.Now() 23 | clientAlert.StartsAt = now 24 | clientAlert.EndsAt = now.Add(a.Expiry) 25 | return a.push(clientAlert) 26 | } 27 | 28 | func (a Alertmanager) Resolve(alert Alert) error { 29 | clientAlert := alert.clientAlert() 30 | now := time.Now() 31 | clientAlert.StartsAt = now 32 | clientAlert.EndsAt = now 33 | return a.push(clientAlert) 34 | } 35 | 36 | // push sends the alerts to all configured Alertmanagers concurrently 37 | // It returns an error if the alerts could not be sent successfully to at least one Alertmanager. 38 | // Somewhat based upon https://github.com/prometheus/prometheus/blob/main/notifier/notifier.go 39 | func (a Alertmanager) push(alert client.Alert) error { 40 | var ( 41 | pushes atomic.Int64 42 | wg sync.WaitGroup 43 | ) 44 | 45 | for _, endpoint := range a.Endpoints { 46 | wg.Add(1) 47 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 48 | defer cancel() 49 | 50 | go func(address string) { 51 | defer wg.Done() 52 | apiClient, err := api.NewClient(api.Config{Address: address}) 53 | if err != nil { 54 | log.Printf("Error configuring apiclient for %s - %s", address, err) 55 | return 56 | } 57 | alertClient := client.NewAlertAPI(apiClient) 58 | err = alertClient.Push(ctx, alert) 59 | if err != nil { 60 | log.Printf("Error pushing alert to %s - %s", address, err) 61 | return 62 | } 63 | pushes.Inc() 64 | }(endpoint) 65 | } 66 | 67 | wg.Wait() 68 | 69 | if pushes.Load() < 1 { 70 | return errors.New("Failed to push alert to any alertmanager") 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /example/prometheus-a.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: prometheus-a 5 | labels: 6 | app: prometheus-a 7 | spec: 8 | ports: 9 | - port: 9090 10 | name: web 11 | clusterIP: None 12 | selector: 13 | app: prometheus-a 14 | --- 15 | apiVersion: apps/v1 16 | kind: StatefulSet 17 | metadata: 18 | name: prometheus-a 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: prometheus-a 23 | serviceName: prometheus-a 24 | replicas: 1 25 | template: 26 | metadata: 27 | labels: 28 | app: prometheus-a 29 | spec: 30 | containers: 31 | - name: prometheus 32 | image: prom/prometheus:v2.24.1 33 | ports: 34 | - containerPort: 9090 35 | name: default 36 | volumeMounts: 37 | - name: config-volume 38 | mountPath: /etc/prometheus 39 | volumes: 40 | - name: config-volume 41 | configMap: 42 | name: prometheus-a-config 43 | --- 44 | kind: ConfigMap 45 | apiVersion: v1 46 | metadata: 47 | name: prometheus-a-config 48 | data: 49 | prometheus.yml: | 50 | global: 51 | external_labels: 52 | team: a 53 | rule_files: 54 | - rules.yml 55 | alerting: 56 | alertmanagers: 57 | - static_configs: 58 | - targets: 59 | - alertmanager-0:9093 60 | - alertmanager-1:9093 61 | rules.yml: | 62 | groups: 63 | - name: watchdog 64 | rules: 65 | - alert: Watchdog 66 | annotations: 67 | description: | 68 | This is an alert meant to ensure that the entire alerting pipeline is functional. 69 | This alert is always firing, therefore it should always be firing in Alertmanager 70 | and always fire against a receiver. There are integrations with various notification 71 | mechanisms that send a notification when this alert is not firing. For example the 72 | "DeadMansSnitch" integration in PagerDuty. 73 | summary: Watchdog 74 | expr: vector(1) 75 | labels: 76 | component: general.rules 77 | service: kubernetes 78 | severity: none 79 | -------------------------------------------------------------------------------- /example/prometheus-b.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: prometheus-b 5 | labels: 6 | app: prometheus-b 7 | spec: 8 | ports: 9 | - port: 9090 10 | name: web 11 | clusterIP: None 12 | selector: 13 | app: prometheus-b 14 | --- 15 | apiVersion: apps/v1 16 | kind: StatefulSet 17 | metadata: 18 | name: prometheus-b 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: prometheus-b 23 | serviceName: prometheus-b 24 | replicas: 1 25 | template: 26 | metadata: 27 | labels: 28 | app: prometheus-b 29 | spec: 30 | containers: 31 | - name: prometheus 32 | image: prom/prometheus:v2.24.1 33 | ports: 34 | - containerPort: 9090 35 | name: default 36 | volumeMounts: 37 | - name: config-volume 38 | mountPath: /etc/prometheus 39 | volumes: 40 | - name: config-volume 41 | configMap: 42 | name: prometheus-b-config 43 | --- 44 | kind: ConfigMap 45 | apiVersion: v1 46 | metadata: 47 | name: prometheus-b-config 48 | data: 49 | prometheus.yml: | 50 | global: 51 | external_labels: 52 | team: b 53 | rule_files: 54 | - rules.yml 55 | alerting: 56 | alertmanagers: 57 | - static_configs: 58 | - targets: 59 | - alertmanager-0:9093 60 | - alertmanager-1:9093 61 | rules.yml: | 62 | groups: 63 | - name: watchdog 64 | rules: 65 | - alert: Watchdog 66 | annotations: 67 | description: | 68 | This is an alert meant to ensure that the entire alerting pipeline is functional. 69 | This alert is always firing, therefore it should always be firing in Alertmanager 70 | and always fire against a receiver. There are integrations with various notification 71 | mechanisms that send a notification when this alert is not firing. For example the 72 | "DeadMansSnitch" integration in PagerDuty. 73 | summary: Watchdog 74 | expr: vector(1) 75 | labels: 76 | component: general.rules 77 | service: kubernetes 78 | severity: none 79 | -------------------------------------------------------------------------------- /docs/getting_started.md: -------------------------------------------------------------------------------- 1 | You can find a full example setup in the `/example` directory. 2 | 3 | You can deploy this example to any prometheus cluster e.g. minikube, docker desktop etc... 4 | 5 | 1. Start by deploying the example: 6 | 7 | ``` 8 | kubectl apply -k example 9 | ``` 10 | 11 | This will deploy 2 prometheus clusters, and a single (2 node) alertmanager cluster, and alertdog. 12 | 13 | ``` 14 | $ kubectl -n alertdog-example get pods 15 | NAME READY STATUS RESTARTS AGE 16 | alertdog-6b686c7cd7-jh599 1/1 Running 0 8s 17 | alertmanager-0 1/1 Running 0 8s 18 | alertmanager-1 1/1 Running 0 7s 19 | prometheus-a-0 1/1 Running 0 8s 20 | prometheus-b-0 1/1 Running 0 8s 21 | ``` 22 | 23 | 2. Check alertmanager 24 | 25 | ``` 26 | kubectl -n alertdog-example port-forward alertmanager-0 9093 27 | ``` 28 | 29 | Check [alertmanager](http://localhost:9093) 30 | 31 | You should see a `Watchdog` alert for each cluster - they are being routed to 32 | alertdog. 33 | 34 | 3. Kill a prometheus cluster 35 | 36 | Remove 1 prometheus to simulate it's failure 37 | 38 | ``` 39 | kubectl -n alertdog-example delete sts prometheus-a 40 | ``` 41 | 42 | 4. Check for an alert 43 | 44 | After the prometheus watchdog alert expires (Around 4 minutes by default) you 45 | should see a new `PrometheusAlertFailure` in alertmanager. 46 | 47 | 5. Fix the broken cluster 48 | 49 | ``` 50 | kubectl apply -k example 51 | ``` 52 | 53 | 6. Check the alerts 54 | 55 | You should see the `Watchdog` alert come back after prometheus has started, 56 | The `PrometheusAlertFailure` alert should be resolved in about a minute. 57 | 58 | 7. Broken alertmanager 59 | 60 | If alertmanager is broken, an incident is raised on PagerDuty. 61 | 62 | In order for this to work you need to create an [Events API Key](https://support.pagerduty.com/docs/generating-api-keys#events-api-keys) for your PagerDuty service. 63 | 64 | edit `example/alertdog-config.yml` and replace `PAGER_DUTY_KEY` with your key. 65 | 66 | Then if alertmanager is broken an incident should be raised after 5m. 67 | 68 | ``` 69 | kubectl -n alertdog-example delete sts alertmanager 70 | ``` 71 | -------------------------------------------------------------------------------- /example/alertmanager.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: alertmanager 5 | labels: 6 | app: alertmanager 7 | spec: 8 | clusterIP: None 9 | selector: 10 | app: alertmanager 11 | --- 12 | apiVersion: v1 13 | kind: Service 14 | metadata: 15 | name: alertmanager-0 16 | labels: 17 | app: alertmanager 18 | spec: 19 | selector: 20 | statefulset.kubernetes.io/pod-name: alertmanager-0 21 | ports: 22 | - port: 9093 23 | name: web 24 | - port: 9094 25 | name: cluster 26 | - port: 9094 27 | name: cluster-udp 28 | protocol: UDP 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: alertmanager-1 34 | labels: 35 | app: alertmanager 36 | spec: 37 | selector: 38 | statefulset.kubernetes.io/pod-name: alertmanager-1 39 | ports: 40 | - port: 9093 41 | name: web 42 | - port: 9094 43 | name: cluster 44 | - port: 9094 45 | name: cluster-udp 46 | protocol: UDP 47 | --- 48 | apiVersion: apps/v1 49 | kind: StatefulSet 50 | metadata: 51 | name: alertmanager 52 | spec: 53 | selector: 54 | matchLabels: 55 | app: alertmanager 56 | serviceName: alertmanager 57 | replicas: 2 58 | template: 59 | metadata: 60 | labels: 61 | app: alertmanager 62 | spec: 63 | containers: 64 | - name: alertmanager 65 | image: prom/alertmanager:v0.21.0 66 | args: 67 | - "--config.file=/etc/alertmanager/alertmanager.yml" 68 | - "--cluster.peer=alertmanager-0:9094" 69 | - "--cluster.peer=alertmanager-1:9094" 70 | ports: 71 | - containerPort: 9093 72 | name: http 73 | - containerPort: 9094 74 | name: cluster-tcp 75 | - containerPort: 9094 76 | name: cluster-udp 77 | protocol: UDP 78 | volumeMounts: 79 | - name: config-volume 80 | mountPath: /etc/alertmanager 81 | volumes: 82 | - name: config-volume 83 | configMap: 84 | name: alertmanager-config 85 | --- 86 | kind: ConfigMap 87 | apiVersion: v1 88 | metadata: 89 | name: alertmanager-config 90 | data: 91 | alertmanager.yml: | 92 | global: {} 93 | receivers: 94 | - name: alertdog 95 | webhook_configs: 96 | - url: http://alertdog:9796/webhook 97 | send_resolved: true 98 | - name: default-receiver 99 | route: 100 | receiver: default-receiver 101 | routes: 102 | - match: 103 | alertname: Watchdog 104 | group_interval: 30s 105 | repeat_interval: 1m 106 | receiver: alertdog 107 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | versionName: 6 | description: 'Release name' 7 | required: true 8 | releaseTag: 9 | description: 'Version tag (format {major}.{minor}.{patch})' 10 | required: true 11 | releaseBody: 12 | description: 'Release body text' 13 | required: true 14 | jobs: 15 | release: 16 | name: Release 17 | runs-on: ubuntu-latest 18 | steps: 19 | - 20 | uses: actions/checkout@v2 21 | - 22 | uses: actions/setup-go@v2 23 | with: 24 | go-version: '^1.16' 25 | - 26 | run: go test -v ./... 27 | - 28 | name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v1 30 | - 31 | name: Login to Public ECR 32 | uses: docker/login-action@v1 33 | with: 34 | registry: public.ecr.aws 35 | username: ${{ secrets.AWS_ACCESS_KEY_ID }} 36 | password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 37 | env: 38 | AWS_REGION: us-east-1 39 | - 40 | name: Login to DockerHub 41 | uses: docker/login-action@v1 42 | with: 43 | username: ${{ secrets.DOCKERHUB_USERNAME }} 44 | password: ${{ secrets.DOCKERHUB_TOKEN }} 45 | - 46 | name: Login to Quay 47 | uses: docker/login-action@v1 48 | with: 49 | registry: quay.io 50 | username: ${{ secrets.QUAY_USERNAME }} 51 | password: ${{ secrets.QUAY_TOKEN }} 52 | - 53 | name: Build and push 54 | id: docker_build 55 | uses: docker/build-push-action@v2 56 | with: 57 | push: true 58 | tags: errm/alertdog:${{ github.event.inputs.releaseTag }},quay.io/errm/alertdog:${{ github.event.inputs.releaseTag }},public.ecr.aws/errm/alertdog:${{ github.event.inputs.releaseTag }} 59 | platforms: linux/amd64,linux/arm64 60 | - 61 | name: Create Release 62 | id: create_release 63 | uses: actions/create-release@v1 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | with: 67 | tag_name: ${{ github.event.inputs.releaseTag }} 68 | release_name: Release ${{ github.event.inputs.versionName }} 69 | body: | 70 | ${{ github.event.inputs.releaseBody }} 71 | 72 | ## Docker images: 73 | 74 | * `errm/alertdog:${{ github.event.inputs.releaseTag }}` 75 | * `quay.io/errm/alertdog:${{ github.event.inputs.releaseTag }}` 76 | * `public.ecr.aws/errm/alertdog:${{ github.event.inputs.releaseTag }}` 77 | draft: false 78 | prerelease: false 79 | -------------------------------------------------------------------------------- /docs/design.md: -------------------------------------------------------------------------------- 1 | # Alertdog Design 2 | 3 | Date: Feb/March 2021 4 | Author: @errm 5 | 6 | ## Overview 7 | 8 | Alertdog is software system to detect failures in a prometheus + alertmanager 9 | alerting system. 10 | 11 | It is a so called ["Dead mans switch"](https://en.wikipedia.org/wiki/Dead_man%27s_switch) 12 | or ["Watchdog timer"](https://en.wikipedia.org/wiki/Watchdog_timer) 13 | 14 | ## Background / context 15 | 16 | Alertdog is being developed for use in Cookpad's global organisation. 17 | 18 | Within Cookpad we have several different teams who operate there own prometheus 19 | clusters to monitor platform (Kubernetes) and application metrics. 20 | 21 | We make use of a single alertmanager cluster to route alerts generated by 22 | all prometheus clusters to the correct team. This alertmanager cluster is 23 | maintained by our SRE team as a service to the other teams. 24 | 25 | ## Goals 26 | 27 | * Alert a particular team if a/the prometheus cluster they operate is not 28 | correctly delivering alerts to alertmanager. 29 | * Make use of existing alertmanager routing and receivers to deliver alerts 30 | based on each teams preferences. e.g. to slack, PagerDuty or email etc... 31 | * Alert the team who manages alertmanager via pagerduty if alertmanager is down. 32 | 33 | ## Proposed design 34 | 35 | * Alertdog will expose a webhook endpoint. 36 | * Prometheus should be configured to produce an always on "Watchdog" alert [e.g.](https://github.com/prometheus-operator/kube-prometheus/blob/1bf43811174355359e5316b52bfb1a0b928550b2/jsonnet/kube-prometheus/components/mixin/alerts/general.libsonnet#L19-L31) for an example of a watchdog alert. 37 | * Alertmanager should be configured to route these alerts to the Alertdog webhook. 38 | 39 | 1. If Alertdog doesn't receive an Watchdog alert for a configured prometheus 40 | instance within a configurable expiry time then we consider that that 41 | prometheus instance is down and trigger an alert within alertmanager. 42 | 1. If Alertdog doesn't receive *any* webhook activity for a configurable expiry 43 | time then we consider that alertmanager is down, and raise a PagerDuty incident. 44 | * If Alertdog encounters errors triggering alerts on alertmanager then, we 45 | consider that alertmanager is down, and raise a PagerDuty incident. 46 | * We will only trigger an incident if we cannot successfully trigger the 47 | alert on all configured alertmanager instances, this is the same as the [prometheus design](https://github.com/prometheus/prometheus/blob/4e5b1722b342948a55b3d7753f6539040db0e5f0/notifier/notifier.go#L449-L450) 48 | 49 | ![diagram of design](./design.jpg) _Thanks @kenju_ 50 | 51 | ## Alternative Designs 52 | 53 | ### https://github.com/tomaszkiewicz/prometheus-alertmanager-cloudwatch-webhook 54 | 55 | * Webhook updates cloudwatch metrics 56 | * Cloudwatch alarms notify when prometheus is down 57 | 58 | ### https://github.com/KierranM/deadmanswatch 59 | 60 | * Similar cloud watch based solution 61 | 62 | ### https://github.com/BarthV/alertmanager-deadman-receiver 63 | 64 | * Most similar design to alertdog 65 | * Alerts directly to slack or PagerDuty, additional receivers would need code changes / extra config. 66 | * Doesn't have configuration for which prometheus clusters should be up, rather alerts when and alert that was being received stops being received. 67 | * This is elegant, but could be unreliable if the process is restarted. 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Alertdog: Watching over your alerting system; Barks if it breaks.](docs/dog.jpg "woof 🐕 - alertmanager is broken") 2 | 3 | [Guard dog](https://www.flickr.com/photos/_pavan_/5519497579) by [`_paVan_`](https://www.flickr.com/photos/_pavan_/) is licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/) 4 | 5 | Alertdog is software system to detect failures in a prometheus + alertmanager 6 | alerting system. 7 | 8 | If there is a problem that means that prometheus, or alertmanager are not working 9 | as expected Alertdog will raise an alert, either via alertmanager, or if that 10 | is not possible via PagerDuty. 11 | 12 | It is designed specifically to meet the needs of an organisation [Cookpad](https://www.cookpadteam.com/) where 13 | several Prometheus clusters are managed by different teams, but 14 | a single alertmanager cluster is utilised. 15 | 16 | ## Design 17 | 18 | You can read detailed information about the design of Alertdog [here](docs/design.md) 19 | 20 | ## Getting started 21 | 22 | To get started with Alertdog check out the [getting started documentation](docs/getting_started.md) 23 | 24 | ## Installation 25 | 26 | Alertdog is released as a docker image to [docker hub](https://hub.docker.com/r/errm/alertdog), [quay.io](https://quay.io/repository/errm/alertdog?tab=tags) and [aws ecr](https://gallery.ecr.aws/errm/alertdog) 27 | 28 | Find details of the released tags [here](https://github.com/errm/alertdog/releases) 29 | 30 | We recommend that you run alertdog with a container orchestrator like Kubernetes 31 | see an example [here](example/alertdog.yml) 32 | 33 | ## Config File Format 34 | 35 | `config.yml` 36 | 37 | ```yaml 38 | # A list of alertmanager endpoints (used when pushing alerts to alertmanager) 39 | alertmanager_endpoints: 40 | - http://alertmanager-0:9093 41 | - http://alertmanager-1:9093 42 | 43 | 44 | # How often Alertdog checks if a Watchdog has been recieved from an expected 45 | # prometheus (optional) (defaults to 2m) 46 | check_interval: 2m 47 | 48 | # How long Alertdog will wait before raising a PagerDuty alert if no webhook 49 | # requests were recieved from alertmanger (optional) (defaults to 5m) 50 | expiry: 5m 51 | 52 | # The port that the webhook endpoint is exposed on (optional) (defaults to 9767) 53 | port: 9767 54 | 55 | # A PagerDuty EventsV2 API routing key 56 | pager_duty_key: PAGER_DUTY_KEY 57 | 58 | # A url for a runbook, to be included in PagerDuty alerts (optional) 59 | pagerduty_runbook_url: https://example.org/alertmanager_down_runbook 60 | 61 | # A list of prometheus clusters that we expect to recieve Watchdog alerts from 62 | expected: 63 | - 64 | # The labels that match this prometheus cluster, should be set to the 65 | # Alertname you use for watchdog alerts, and any external labels set on 66 | # this cluster. Note you need to make sure that your cluster have unique labels. 67 | match_labels: 68 | alertname: Watchdog 69 | owner: team-a 70 | 71 | # How long to wait after reciving a watchdog before raising an alert (optional) (defaults to 4m) 72 | # Note this value should be longer than `check_interval`. 73 | # Make sure that alertmanager repeats the alert at least this often, 74 | # by setting `repeat_interval` & `group_interval` to appropriate values in 75 | # the alertmanger route configuration see example/alertmanger.yml for an example of this. 76 | expiry: 4m 77 | 78 | # The configuration of the alert that will be raised in alertmanager if the 79 | # Watchdog isn't recieved within the configured expiry time 80 | alert: 81 | name: PrometheusAlertFailure 82 | labels: 83 | severity: critical 84 | owner: team-a 85 | service: monitoring 86 | component: prometheus 87 | annotations: 88 | runbook: https://example.org/prometheus-alert-failure 89 | description: | 90 | This alert fires when the "Watchdog" alert fails to be received 91 | from a prometheus instance. 92 | It could indicate that the prometheus instance is not running, or 93 | that there is a configuration issue with prometheus or alertmanager. 94 | ``` 95 | 96 | ## Contributing 97 | 98 | * If you find a bug please raise an issue. 99 | * If it's security related please contact me on: edward-robinson@cookpad.com [GPG key here](https://keybase.io/errm) 100 | 101 | * PRs are welcome :) 102 | * If you open a PR from your own fork the Test and Lint github actions should be working 103 | * The repo also has a PR action that attempts to push a built container image to Docker Hub 104 | * This action is so you / I can do some manual testing if required! 105 | * Your fork won't have the secret with credentials for my docker hub :) 106 | * If you want it to work on your fork: 107 | * Create an repo in your DockerHub, set it's name in the `DOCKERHUB_PR_REPO` on your fork's secrets. 108 | * Create a DockerHub token that can push to that repo 109 | * Set the `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` secrets on your fork 110 | -------------------------------------------------------------------------------- /pkg/alertdog/alertdog.go: -------------------------------------------------------------------------------- 1 | package alertdog 2 | 3 | import ( 4 | "os" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "sync" 10 | "time" 11 | 12 | "github.com/PagerDuty/go-pagerduty" 13 | "github.com/prometheus/alertmanager/template" 14 | 15 | "github.com/errm/alertdog/pkg/alertmanager" 16 | ) 17 | 18 | type Alertmanager interface { 19 | Alert(alertmanager.Alert) error 20 | Resolve(alertmanager.Alert) error 21 | } 22 | 23 | type Pagerduty interface { 24 | ManageEvent(event pagerduty.V2Event) (*pagerduty.V2EventResponse, error) 25 | } 26 | 27 | type Alertdog struct { 28 | AlertmanagerEndpoints []string `yaml:"alertmanager_endpoints"` 29 | Expected []*Prometheus 30 | CheckInterval time.Duration `yaml:"check_interval"` 31 | Expiry time.Duration 32 | Port uint 33 | PagerDutyKey string `yaml:"pager_duty_key"` 34 | PagerDutyRunbookURL string `yaml:"pagerduty_runbook_url"` 35 | 36 | mu sync.RWMutex 37 | checkedIn time.Time 38 | alertmanager Alertmanager 39 | pagerduty Pagerduty 40 | } 41 | 42 | func (a *Alertdog) UnmarshalYAML(unmarshal func(interface{}) error) error { 43 | defaultInterval, _ := time.ParseDuration("2m") 44 | a.CheckInterval = defaultInterval 45 | defaultExpiry, _ := time.ParseDuration("5m") 46 | a.Expiry = defaultExpiry 47 | // https://github.com/prometheus/prometheus/wiki/Default-port-allocations 48 | a.Port = 9796 49 | a.PagerDutyKey = os.Getenv("PAGER_DUTY_KEY") 50 | type plain Alertdog 51 | return unmarshal((*plain)(a)) 52 | } 53 | 54 | func (a *Alertdog) Setup() { 55 | a.alertmanager = alertmanager.Alertmanager{Endpoints: a.AlertmanagerEndpoints, Expiry: a.CheckInterval * 2} 56 | a.pagerduty = PagerdutyClient{} 57 | } 58 | 59 | func (a *Alertdog) ServeHTTP(w http.ResponseWriter, r *http.Request) { 60 | defer r.Body.Close() 61 | var data template.Data 62 | if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 63 | log.Printf("Webhook body invalid, skipping request: %s", err) 64 | w.WriteHeader(http.StatusBadRequest) 65 | return 66 | } 67 | for _, alert := range data.Alerts { 68 | a.processWatchdog(alert) 69 | } 70 | w.WriteHeader(http.StatusOK) 71 | } 72 | 73 | func (a *Alertdog) processWatchdog(alert template.Alert) { 74 | a.CheckIn() 75 | for _, prometheus := range a.Expected { 76 | var err error 77 | switch action := prometheus.CheckIn(alert); action { 78 | case ActionAlert: 79 | err = a.alertmanager.Alert(prometheus.Alert) 80 | case ActionResolve: 81 | err = a.alertmanager.Resolve(prometheus.Alert) 82 | } 83 | if err != nil { 84 | a.pagerDutyAlert("alertdog:alertmanager-push", "Alertdog cannot push alerts to alertmanager") 85 | } 86 | } 87 | } 88 | 89 | func (a *Alertdog) CheckLoop() { 90 | checkExpiryTicker := time.NewTicker(a.CheckInterval) 91 | for { 92 | <-checkExpiryTicker.C 93 | a.Check() 94 | } 95 | } 96 | 97 | func (a *Alertdog) Check() { 98 | for _, prometheus := range a.Expected { 99 | if action := prometheus.Check(); action == ActionAlert { 100 | if err := a.alertmanager.Alert(prometheus.Alert); err != nil { 101 | a.pagerDutyAlert("alertdog:alertmanager-push", "Alertdog cannot push alerts to alertmanager") 102 | } 103 | } 104 | } 105 | if a.Expired() { 106 | a.pagerDutyAlert( 107 | "alertdog:webhook-expiry", 108 | fmt.Sprintf("Alertdog: didn't receive webhook from alert manager for over %v", a.Expiry), 109 | ) 110 | } else { 111 | a.pagerDutyResolve("alertdog:webhook-expiry") 112 | } 113 | } 114 | 115 | func (a *Alertdog) CheckIn() { 116 | a.mu.Lock() 117 | defer a.mu.Unlock() 118 | a.checkedIn = time.Now() 119 | } 120 | 121 | func (a *Alertdog) Expired() bool { 122 | a.mu.RLock() 123 | defer a.mu.RUnlock() 124 | return time.Now().After(a.checkedIn.Add(a.Expiry)) 125 | } 126 | 127 | func (a *Alertdog) pagerDutyAlert(dedupKey, summary string) { 128 | log.Println("PagerDuty: ", summary) 129 | event := pagerduty.V2Event{ 130 | Action: "trigger", 131 | RoutingKey: a.PagerDutyKey, 132 | DedupKey: dedupKey, 133 | Payload: &pagerduty.V2Payload{ 134 | Summary: summary, 135 | Source: dedupKey, 136 | Severity: "critical", 137 | }, 138 | Images: []interface{}{ 139 | map[string]string{ 140 | "src": "https://github.com/errm/alertdog/raw/main/docs/dog.jpg", 141 | }, 142 | }, 143 | } 144 | if a.PagerDutyRunbookURL != "" { 145 | event.Links = []interface{}{ 146 | map[string]string{ 147 | "text": "Runbook 📕", 148 | "href": a.PagerDutyRunbookURL, 149 | }, 150 | } 151 | } 152 | if response, err := a.pagerduty.ManageEvent(event); err != nil { 153 | log.Printf("Error raising alert on pagerduty: %s %+v", err, response) 154 | } 155 | } 156 | 157 | func (a *Alertdog) pagerDutyResolve(dedupKey string) { 158 | event := pagerduty.V2Event{ 159 | Action: "resolve", 160 | RoutingKey: a.PagerDutyKey, 161 | DedupKey: dedupKey, 162 | } 163 | if response, err := a.pagerduty.ManageEvent(event); err != nil { 164 | log.Printf("Error resolving alert on pagerduty: %s %+v", err, response) 165 | } 166 | } 167 | 168 | type PagerdutyClient struct{} 169 | 170 | func (p PagerdutyClient) ManageEvent(event pagerduty.V2Event) (*pagerduty.V2EventResponse, error) { 171 | return pagerduty.ManageEvent(event) 172 | } 173 | -------------------------------------------------------------------------------- /pkg/alertmanager/alertmanager_test.go: -------------------------------------------------------------------------------- 1 | package alertmanager 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "reflect" 9 | "testing" 10 | "time" 11 | 12 | "github.com/prometheus/alertmanager/client" 13 | "github.com/stretchr/testify/require" 14 | "go.uber.org/atomic" 15 | ) 16 | 17 | func TestAlert(t *testing.T) { 18 | var ( 19 | errc = make(chan error, 1) 20 | expected = make([]*client.Alert, 0, 1) 21 | status1, status2 atomic.Int32 22 | slow1, slow2 atomic.Bool 23 | ) 24 | 25 | status1.Store(int32(http.StatusOK)) 26 | status2.Store(int32(http.StatusOK)) 27 | 28 | newHTTPServer := func(status *atomic.Int32, slow *atomic.Bool, checkAlerts func([]*client.Alert, []*client.Alert) error) *httptest.Server { 29 | 30 | return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 31 | var err error 32 | defer func() { 33 | if err == nil { 34 | return 35 | } 36 | select { 37 | case errc <- err: 38 | default: 39 | } 40 | }() 41 | var alerts []*client.Alert 42 | 43 | err = json.NewDecoder(r.Body).Decode(&alerts) 44 | if err == nil { 45 | err = checkAlerts(expected, alerts) 46 | } 47 | s := int(status.Load()) 48 | w.WriteHeader(s) 49 | if slow.Load() { 50 | time.Sleep(11 * time.Second) 51 | } 52 | if s == http.StatusOK { 53 | if _, err := w.Write([]byte("{\"status\":\"success\"}")); err != nil { 54 | panic(err) 55 | } 56 | } else { 57 | if _, err := w.Write([]byte("{\"status\":\"error\"}")); err != nil { 58 | panic(err) 59 | } 60 | } 61 | })) 62 | } 63 | 64 | server1 := newHTTPServer(&status1, &slow1, alertsOK) 65 | server2 := newHTTPServer(&status2, &slow2, alertsOK) 66 | 67 | defer server1.Close() 68 | defer server2.Close() 69 | 70 | alertManager := Alertmanager{ 71 | Endpoints: []string{ 72 | server1.URL, 73 | server2.URL, 74 | }, 75 | Expiry: time.Minute, 76 | } 77 | 78 | checkNoErr := func() { 79 | t.Helper() 80 | select { 81 | case err := <-errc: 82 | require.NoError(t, err) 83 | default: 84 | } 85 | } 86 | 87 | expected = append(expected, &client.Alert{ 88 | Labels: toLabelSet(map[string]string{ 89 | "alertname": "PrometheusAlertFailure", 90 | "foo": "bar", 91 | }), 92 | }) 93 | 94 | // Both servers OK 95 | require.NoError(t, alertManager.Alert(Alert{ 96 | Name: "PrometheusAlertFailure", 97 | Labels: map[string]string{ 98 | "foo": "bar", 99 | }, 100 | }), "Alerting failed unexpectedly") 101 | checkNoErr() 102 | 103 | // Only one server erring 104 | status2.Store(int32(http.StatusInternalServerError)) 105 | require.NoError(t, alertManager.Alert(Alert{ 106 | Name: "PrometheusAlertFailure", 107 | Labels: map[string]string{ 108 | "foo": "bar", 109 | }, 110 | }), "Alerting failed unexpectedly") 111 | checkNoErr() 112 | 113 | // Both servers error 114 | status1.Store(int32(http.StatusNotFound)) 115 | require.Error(t, alertManager.Alert(Alert{ 116 | Name: "PrometheusAlertFailure", 117 | Labels: map[string]string{ 118 | "foo": "bar", 119 | }, 120 | }), "Alerting succeeded unexpectedly") 121 | checkNoErr() 122 | 123 | //Timeout 124 | status1.Store(int32(http.StatusOK)) 125 | status2.Store(int32(http.StatusOK)) 126 | slow1.Store(true) 127 | slow2.Store(true) 128 | require.Error(t, alertManager.Alert(Alert{ 129 | Name: "PrometheusAlertFailure", 130 | Labels: map[string]string{ 131 | "foo": "bar", 132 | }, 133 | }), "Alerting succeeded unexpectedly") 134 | checkNoErr() 135 | 136 | //Dead server 137 | server1.Close() 138 | server2.Close() 139 | require.Error(t, alertManager.Alert(Alert{ 140 | Name: "PrometheusAlertFailure", 141 | Labels: map[string]string{ 142 | "foo": "bar", 143 | }, 144 | }), "Alerting succeeded unexpectedly") 145 | 146 | // Resolve 147 | server1 = newHTTPServer(&status1, &slow1, resolveOK) 148 | server2 = newHTTPServer(&status2, &slow2, resolveOK) 149 | defer server1.Close() 150 | defer server2.Close() 151 | 152 | alertManager = Alertmanager{ 153 | Endpoints: []string{ 154 | server1.URL, 155 | server2.URL, 156 | }, 157 | } 158 | 159 | status1.Store(int32(http.StatusOK)) 160 | status2.Store(int32(http.StatusOK)) 161 | slow1.Store(false) 162 | slow2.Store(false) 163 | 164 | require.NoError(t, alertManager.Resolve(Alert{ 165 | Name: "PrometheusAlertFailure", 166 | Labels: map[string]string{ 167 | "foo": "bar", 168 | }, 169 | }), "Alerting succeeded unexpectedly") 170 | } 171 | 172 | func alertsOK(expected, actual []*client.Alert) error { 173 | if len(expected) != len(actual) { 174 | return fmt.Errorf("length mismatch: %v != %v", expected, actual) 175 | } 176 | for i, alert := range expected { 177 | if !labelsEqual(alert.Labels, actual[i].Labels) { 178 | return fmt.Errorf("label mismatch at index %d: %s != %s", i, alert.Labels, actual[i].Labels) 179 | } 180 | } 181 | for _, alert := range actual { 182 | if alert.EndsAt != alert.StartsAt.Add(time.Minute) { 183 | return fmt.Errorf("Expected EndsAt to be %s was %s", alert.StartsAt.Add(time.Minute), alert.EndsAt) 184 | } 185 | } 186 | return nil 187 | } 188 | 189 | func resolveOK(expected, actual []*client.Alert) error { 190 | if len(expected) != len(actual) { 191 | return fmt.Errorf("length mismatch: %v != %v", expected, actual) 192 | } 193 | for i, alert := range expected { 194 | if !labelsEqual(alert.Labels, actual[i].Labels) { 195 | return fmt.Errorf("label mismatch at index %d: %s != %s", i, alert.Labels, expected[i].Labels) 196 | } 197 | } 198 | for _, alert := range actual { 199 | if alert.EndsAt != alert.StartsAt { 200 | return fmt.Errorf("Expected EndsAt to equal StartsAt %s vs %s", alert.EndsAt, alert.StartsAt) 201 | } 202 | } 203 | return nil 204 | } 205 | 206 | func labelsEqual(a, b client.LabelSet) bool { 207 | return reflect.DeepEqual(a, b) 208 | } 209 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pkg/alertdog/alertdog_test.go: -------------------------------------------------------------------------------- 1 | package alertdog 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/PagerDuty/go-pagerduty" 9 | "github.com/prometheus/alertmanager/template" 10 | "github.com/stretchr/testify/mock" 11 | 12 | "github.com/errm/alertdog/pkg/alertmanager" 13 | ) 14 | 15 | type AlertmanagerMock struct { 16 | mock.Mock 17 | } 18 | 19 | func (a *AlertmanagerMock) Alert(alert alertmanager.Alert) error { 20 | args := a.Called(alert) 21 | return args.Error(0) 22 | } 23 | 24 | func (a *AlertmanagerMock) Resolve(alert alertmanager.Alert) error { 25 | args := a.Called(alert) 26 | return args.Error(0) 27 | } 28 | 29 | type PagerdutyMock struct { 30 | mock.Mock 31 | } 32 | 33 | func (p *PagerdutyMock) ManageEvent(event pagerduty.V2Event) (*pagerduty.V2EventResponse, error) { 34 | args := p.Called(event) 35 | response := &pagerduty.V2EventResponse{} 36 | return response, args.Error(0) 37 | } 38 | 39 | type expectation struct { 40 | method string 41 | arg interface{} 42 | err error 43 | } 44 | 45 | func TestProcessWatchdog(t *testing.T) { 46 | alert1 := alertmanager.Alert{ 47 | Labels: map[string]string{ 48 | "alert": "one", 49 | }, 50 | } 51 | 52 | prom1 := &Prometheus{ 53 | MatchLabels: map[string]string{ 54 | "alertname": "Watchdog", 55 | "prometheus": "prom1", 56 | }, 57 | Alert: alert1, 58 | } 59 | 60 | alert2 := alertmanager.Alert{ 61 | Labels: map[string]string{ 62 | "alert": "two", 63 | }, 64 | } 65 | 66 | prom2 := &Prometheus{ 67 | MatchLabels: map[string]string{ 68 | "alertname": "Watchdog", 69 | "prometheus": "prom2", 70 | }, 71 | Alert: alert2, 72 | } 73 | 74 | alertdog := Alertdog{Expected: []*Prometheus{prom1, prom2}, PagerDutyKey: "pagerduty-key", PagerDutyRunbookURL: "https://example.org/runbook-url"} 75 | 76 | error := errors.New("alertmanager is broken") 77 | 78 | pagerDutyEvent := pagerduty.V2Event{ 79 | Action: "trigger", 80 | RoutingKey: "pagerduty-key", 81 | DedupKey: "alertdog:alertmanager-push", 82 | Payload: &pagerduty.V2Payload{ 83 | Summary: "Alertdog cannot push alerts to alertmanager", 84 | Source: "alertdog:alertmanager-push", 85 | Severity: "critical", 86 | }, 87 | Images: []interface{}{ 88 | map[string]string{ 89 | "src": "https://github.com/errm/alertdog/raw/main/docs/dog.jpg", 90 | }, 91 | }, 92 | Links: []interface{}{ 93 | map[string]string{ 94 | "text": "Runbook 📕", 95 | "href": "https://example.org/runbook-url", 96 | }, 97 | }, 98 | } 99 | 100 | var tests = []struct { 101 | description string 102 | expectations []expectation 103 | watchdogs []template.Alert 104 | pagerdutyExpectations []expectation 105 | }{ 106 | { 107 | description: "When we receive a resolved watchdog: alert with the correct alert", 108 | expectations: []expectation{expectation{method: "Alert", arg: alert1}}, 109 | watchdogs: []template.Alert{ 110 | template.Alert{ 111 | Status: "resolved", 112 | Labels: template.KV{ 113 | "alertname": "Watchdog", 114 | "prometheus": "prom1", 115 | }, 116 | }, 117 | }, 118 | }, 119 | { 120 | description: "Fire the correct alert based on labels", 121 | expectations: []expectation{expectation{method: "Alert", arg: alert2}}, 122 | watchdogs: []template.Alert{ 123 | template.Alert{ 124 | Status: "resolved", 125 | Labels: template.KV{ 126 | "alertname": "Watchdog", 127 | "prometheus": "prom2", 128 | }, 129 | }, 130 | }, 131 | }, 132 | { 133 | description: "Don't care about extra labels", 134 | expectations: []expectation{expectation{method: "Alert", arg: alert2}}, 135 | watchdogs: []template.Alert{ 136 | template.Alert{ 137 | Status: "resolved", 138 | Labels: template.KV{ 139 | "alertname": "Watchdog", 140 | "prometheus": "prom2", 141 | "foo": "bar", 142 | }, 143 | }, 144 | }, 145 | }, 146 | { 147 | description: "Don't do anything with watchdogs that don't match", 148 | watchdogs: []template.Alert{ 149 | template.Alert{ 150 | Status: "resolved", 151 | Labels: template.KV{ 152 | "alertname": "Watchdog", 153 | "prometheus": "prom33", 154 | }, 155 | }, 156 | template.Alert{ 157 | Status: "firing", 158 | Labels: template.KV{ 159 | "alertname": "Watchdog", 160 | "prometheus": "prom33", 161 | }, 162 | }, 163 | template.Alert{ 164 | Status: "firing", 165 | Labels: template.KV{ 166 | "alertname": "Watchdog", 167 | "prometheus": "prom33", 168 | }, 169 | }, 170 | }, 171 | }, 172 | { 173 | description: "When we receive a firing watchdog twice: resolve the correct alert", 174 | expectations: []expectation{expectation{method: "Resolve", arg: alert1}}, 175 | watchdogs: []template.Alert{ 176 | template.Alert{ 177 | Status: "firing", 178 | Labels: template.KV{ 179 | "alertname": "Watchdog", 180 | "prometheus": "prom1", 181 | }, 182 | }, 183 | template.Alert{ 184 | Status: "firing", 185 | Labels: template.KV{ 186 | "alertname": "Watchdog", 187 | "prometheus": "prom1", 188 | }, 189 | }, 190 | }, 191 | }, 192 | { 193 | description: "When alertmanager errors, raise a pagerduty event", 194 | expectations: []expectation{expectation{method: "Alert", arg: alert1, err: error}}, 195 | pagerdutyExpectations: []expectation{expectation{method: "ManageEvent", arg: pagerDutyEvent}}, 196 | watchdogs: []template.Alert{ 197 | template.Alert{ 198 | Status: "resolved", 199 | Labels: template.KV{ 200 | "alertname": "Watchdog", 201 | "prometheus": "prom1", 202 | }, 203 | }, 204 | }, 205 | }, 206 | { 207 | description: "Don't flap on a single watchdog", 208 | // This can happen when prometheus goes down 209 | // if the watchdog resolves slightly earlier 210 | // on one alertmanager in a ha pair. 211 | expectations: []expectation{ 212 | expectation{method: "Alert", arg: alert2}, 213 | }, 214 | watchdogs: []template.Alert{ 215 | template.Alert{ 216 | Status: "resolved", 217 | Labels: template.KV{ 218 | "alertname": "Watchdog", 219 | "prometheus": "prom2", 220 | }, 221 | }, 222 | template.Alert{ 223 | Status: "firing", 224 | Labels: template.KV{ 225 | "alertname": "Watchdog", 226 | "prometheus": "prom2", 227 | }, 228 | }, 229 | template.Alert{ 230 | Status: "resolved", 231 | Labels: template.KV{ 232 | "alertname": "Watchdog", 233 | "prometheus": "prom2", 234 | }, 235 | }, 236 | }, 237 | }, 238 | } 239 | 240 | for _, test := range tests { 241 | alertmanagerMock := &AlertmanagerMock{} 242 | alertdog.alertmanager = alertmanagerMock 243 | for _, expectation := range test.expectations { 244 | alertmanagerMock.On(expectation.method, expectation.arg).Return(expectation.err) 245 | } 246 | 247 | pagerdutyMock := &PagerdutyMock{} 248 | alertdog.pagerduty = pagerdutyMock 249 | 250 | for _, expectation := range test.pagerdutyExpectations { 251 | pagerdutyMock.On(expectation.method, expectation.arg).Return(expectation.err) 252 | } 253 | 254 | for _, watchdog := range test.watchdogs { 255 | alertdog.processWatchdog(watchdog) 256 | } 257 | 258 | alertmanagerMock.AssertExpectations(t) 259 | pagerdutyMock.AssertExpectations(t) 260 | } 261 | } 262 | 263 | func TestCheck(t *testing.T) { 264 | alert1 := alertmanager.Alert{ 265 | Labels: map[string]string{ 266 | "alert": "one", 267 | }, 268 | } 269 | 270 | alert2 := alertmanager.Alert{ 271 | Labels: map[string]string{ 272 | "alert": "two", 273 | }, 274 | } 275 | 276 | pagerDutyEvent := pagerduty.V2Event{ 277 | Action: "trigger", 278 | RoutingKey: "this-is-a-key", 279 | DedupKey: "alertdog:webhook-expiry", 280 | Payload: &pagerduty.V2Payload{ 281 | Summary: "Alertdog: didn't receive webhook from alert manager for over 2m0s", 282 | Source: "alertdog:webhook-expiry", 283 | Severity: "critical", 284 | }, 285 | Images: []interface{}{ 286 | map[string]string{ 287 | "src": "https://github.com/errm/alertdog/raw/main/docs/dog.jpg", 288 | }, 289 | }, 290 | } 291 | 292 | pagerDutyResolveEvent := pagerduty.V2Event{ 293 | Action: "resolve", 294 | RoutingKey: "this-is-a-key", 295 | DedupKey: "alertdog:webhook-expiry", 296 | } 297 | 298 | var tests = []struct { 299 | description string 300 | expectations []expectation 301 | pagerdutyExpectations []expectation 302 | watchdogs []template.Alert 303 | }{ 304 | { 305 | description: "If no watchdogs are received, then fire all alerts, and raise a pagerduty incident", 306 | expectations: []expectation{ 307 | expectation{method: "Alert", arg: alert1}, 308 | expectation{method: "Alert", arg: alert2}, 309 | }, 310 | pagerdutyExpectations: []expectation{expectation{method: "ManageEvent", arg: pagerDutyEvent}}, 311 | }, 312 | { 313 | description: "Fire the alert if the watchdog was missing", 314 | expectations: []expectation{ 315 | expectation{method: "Alert", arg: alert1}, 316 | }, 317 | watchdogs: []template.Alert{ 318 | template.Alert{ 319 | Status: "firing", 320 | Labels: template.KV{ 321 | "alertname": "Watchdog", 322 | "prometheus": "prom2", 323 | }, 324 | }, 325 | }, 326 | pagerdutyExpectations: []expectation{expectation{method: "ManageEvent", arg: pagerDutyResolveEvent}}, 327 | }, 328 | { 329 | description: "Don't fire if watchdogs were received", 330 | watchdogs: []template.Alert{ 331 | template.Alert{ 332 | Status: "firing", 333 | Labels: template.KV{ 334 | "alertname": "Watchdog", 335 | "prometheus": "prom2", 336 | }, 337 | }, 338 | template.Alert{ 339 | Status: "firing", 340 | Labels: template.KV{ 341 | "alertname": "Watchdog", 342 | "prometheus": "prom1", 343 | }, 344 | }, 345 | }, 346 | pagerdutyExpectations: []expectation{expectation{method: "ManageEvent", arg: pagerDutyResolveEvent}}, 347 | }, 348 | { 349 | description: "Fire if only resolves where received", 350 | expectations: []expectation{ 351 | expectation{method: "Alert", arg: alert1}, 352 | expectation{method: "Alert", arg: alert2}, 353 | }, 354 | watchdogs: []template.Alert{ 355 | template.Alert{ 356 | Status: "resolved", 357 | Labels: template.KV{ 358 | "alertname": "Watchdog", 359 | "prometheus": "prom2", 360 | }, 361 | }, 362 | template.Alert{ 363 | Status: "resolved", 364 | Labels: template.KV{ 365 | "alertname": "Watchdog", 366 | "prometheus": "prom1", 367 | }, 368 | }, 369 | }, 370 | pagerdutyExpectations: []expectation{expectation{method: "ManageEvent", arg: pagerDutyResolveEvent}}, 371 | }, 372 | } 373 | 374 | for _, test := range tests { 375 | t.Run(test.description, func(t *testing.T) { 376 | alertmanagerMock := &AlertmanagerMock{} 377 | pagerdutyMock := &PagerdutyMock{} 378 | 379 | alertdog := Alertdog{ 380 | Expected: []*Prometheus{ 381 | &Prometheus{ 382 | MatchLabels: map[string]string{ 383 | "alertname": "Watchdog", 384 | "prometheus": "prom1", 385 | }, 386 | Alert: alert1, 387 | Expiry: time.Minute, 388 | }, 389 | &Prometheus{ 390 | MatchLabels: map[string]string{ 391 | "alertname": "Watchdog", 392 | "prometheus": "prom2", 393 | }, 394 | Alert: alert2, 395 | Expiry: time.Minute, 396 | }, 397 | }, 398 | PagerDutyKey: "this-is-a-key", 399 | Expiry: time.Minute * 2, 400 | pagerduty: pagerdutyMock, 401 | alertmanager: alertmanagerMock, 402 | } 403 | 404 | for _, expectation := range test.expectations { 405 | alertmanagerMock.On(expectation.method, expectation.arg).Return(expectation.err) 406 | } 407 | 408 | for _, expectation := range test.pagerdutyExpectations { 409 | pagerdutyMock.On(expectation.method, expectation.arg).Return(expectation.err) 410 | } 411 | 412 | for _, watchdog := range test.watchdogs { 413 | alertdog.processWatchdog(watchdog) 414 | } 415 | 416 | alertdog.Check() 417 | alertmanagerMock.AssertExpectations(t) 418 | pagerdutyMock.AssertExpectations(t) 419 | }) 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 5 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 6 | github.com/PagerDuty/go-pagerduty v1.3.0 h1:2vWajLxpmGeP8pmsyZ0MjFneHa8ASJrztJRSe3FNOzg= 7 | github.com/PagerDuty/go-pagerduty v1.3.0/go.mod h1:W5hSIIPrzSgAkNBDiuymWN5g9yQVzimL7BUBL44f3RY= 8 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 9 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 10 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 11 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 12 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 13 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= 14 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= 15 | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= 16 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 17 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 18 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 19 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 20 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 21 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 22 | github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 23 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 24 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 25 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 26 | github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= 27 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 28 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 29 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= 30 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= 31 | github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 32 | github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= 33 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 34 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 35 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 36 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 37 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 38 | github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= 39 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 40 | github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= 41 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 42 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 43 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 44 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 45 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 46 | github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= 47 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 48 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 49 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= 50 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 51 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 52 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 53 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 54 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 55 | github.com/creasty/defaults v1.5.1/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY= 56 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 57 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 58 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 59 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 60 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 61 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 62 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 63 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 64 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 65 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 66 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 67 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 68 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 69 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 70 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 71 | github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= 72 | github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= 73 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 74 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 75 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 76 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= 77 | github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 78 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 79 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 80 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 81 | github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= 82 | github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= 83 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 84 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 85 | github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= 86 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 87 | github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= 88 | github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 89 | github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 90 | github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= 91 | github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= 92 | github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= 93 | github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= 94 | github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 95 | github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 96 | github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 97 | github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 98 | github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 99 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 100 | github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 101 | github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 102 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 103 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 104 | github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 105 | github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 106 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 107 | github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 108 | github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 109 | github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 110 | github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= 111 | github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= 112 | github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= 113 | github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= 114 | github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= 115 | github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= 116 | github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= 117 | github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= 118 | github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 119 | github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 120 | github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= 121 | github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= 122 | github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= 123 | github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= 124 | github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 125 | github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 126 | github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= 127 | github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= 128 | github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= 129 | github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= 130 | github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= 131 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 132 | github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 133 | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 134 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 135 | github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= 136 | github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= 137 | github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 138 | github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= 139 | github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= 140 | github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= 141 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 142 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 143 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 144 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= 145 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 146 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 147 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= 148 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= 149 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= 150 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 151 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 152 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= 153 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 154 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 155 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= 156 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= 157 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= 158 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= 159 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= 160 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= 161 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= 162 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= 163 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= 164 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 165 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 166 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 167 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 168 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= 169 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= 170 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= 171 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= 172 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 173 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 174 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 175 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 176 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 177 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 178 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 179 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 180 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 181 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 182 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 183 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 184 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 185 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 186 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 187 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 188 | github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= 189 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 190 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 191 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 192 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 193 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 194 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 195 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 196 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 197 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 198 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 199 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 200 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 201 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 202 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 203 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 204 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 205 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 206 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 207 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 208 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 209 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 210 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 211 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 212 | github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= 213 | github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 214 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 215 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 216 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 217 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 218 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 219 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 220 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 221 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 222 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 223 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 224 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 225 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 226 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 227 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 228 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 229 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 230 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 231 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 232 | github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= 233 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 234 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 235 | github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= 236 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 237 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= 238 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 239 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 240 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 241 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 242 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 243 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 244 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 245 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 246 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 247 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 248 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= 249 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 250 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 251 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 252 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 253 | github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 254 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 255 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 256 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 257 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 258 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 259 | github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 260 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 261 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 262 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 263 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= 264 | github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= 265 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= 266 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 267 | github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 268 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 269 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 270 | github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= 271 | github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= 272 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 273 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= 274 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 275 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 276 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 277 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 278 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 279 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 280 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 281 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 282 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 283 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 284 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 285 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 286 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 287 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 288 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 289 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 290 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 291 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 292 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 293 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 294 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 295 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 296 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 297 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= 298 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 299 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= 300 | github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= 301 | github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= 302 | github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= 303 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 304 | github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 305 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 306 | github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= 307 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 308 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 309 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 310 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 311 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 312 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 313 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 314 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= 315 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= 316 | github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= 317 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 318 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 319 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= 320 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 321 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 322 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 323 | github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= 324 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 325 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 326 | github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= 327 | github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= 328 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= 329 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 330 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 331 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 332 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 333 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 334 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 335 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 336 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 337 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 338 | github.com/prometheus/alertmanager v0.21.0 h1:qK51JcUR9l/unhawGA9F9B64OCYfcGewhPNprem/Acc= 339 | github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go= 340 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 341 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 342 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 343 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= 344 | github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A= 345 | github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= 346 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 347 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 348 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 349 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 350 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 351 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 352 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 353 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 354 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 355 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= 356 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 357 | github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= 358 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 359 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 360 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 361 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 362 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 363 | github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= 364 | github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 365 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 366 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 367 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 368 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 369 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 370 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 371 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 372 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 373 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 374 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= 375 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 376 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 377 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 378 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= 379 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= 380 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 381 | github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= 382 | github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= 383 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 384 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 385 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 386 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 387 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 388 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 389 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 390 | github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= 391 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 392 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 393 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 394 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 395 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 396 | github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 397 | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= 398 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 399 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 400 | github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= 401 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 402 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 403 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 404 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 405 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 406 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 407 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 408 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 409 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 410 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 411 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 412 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 413 | github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= 414 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= 415 | github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= 416 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 417 | github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg= 418 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 419 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 420 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= 421 | go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 422 | go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 423 | go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 424 | go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= 425 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 426 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 427 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 428 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 429 | go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= 430 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 431 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 432 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 433 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 434 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 435 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 436 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 437 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 438 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 439 | golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 440 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 441 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 442 | golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 443 | golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 444 | golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 445 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 446 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 447 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 448 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 449 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 450 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 451 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 452 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 453 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 454 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 455 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 456 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 457 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 458 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 459 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 460 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 461 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 462 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 463 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 464 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 465 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 466 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 467 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 468 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 469 | golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 470 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 471 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 472 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 473 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 474 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 475 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 476 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 477 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 478 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= 479 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 480 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 481 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 482 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 483 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 484 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 485 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 486 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 487 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 488 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 489 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 490 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 491 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 492 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 493 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 494 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 495 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 496 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 497 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 498 | golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 499 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 500 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 501 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 502 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 503 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 504 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 505 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 506 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 507 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 508 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 509 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 510 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 511 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 512 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 513 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 514 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= 515 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 516 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 517 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 518 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 519 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 520 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 521 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 522 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 523 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 524 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 525 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 526 | golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 527 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 528 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 529 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 530 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 531 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 532 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 533 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 534 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 535 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 536 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 537 | golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 538 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 539 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 540 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 541 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 542 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 543 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 544 | golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 545 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 546 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 547 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 548 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 549 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 550 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 551 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 552 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 553 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 554 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 555 | google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 556 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 557 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 558 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 559 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= 560 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 561 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 562 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 563 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 564 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 565 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 566 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 567 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 568 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 569 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 570 | google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= 571 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 572 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 573 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 574 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 575 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 576 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 577 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 578 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 579 | gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= 580 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 581 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 582 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 583 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 584 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 585 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 586 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 587 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 588 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 589 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 590 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 591 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 592 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 593 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 594 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 595 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 596 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= 597 | --------------------------------------------------------------------------------