├── VERSION ├── .dockerignore ├── CHANGELOG.md ├── .golangci.yml ├── .gitignore ├── .yamllint ├── scripts └── errcheck_excludes.txt ├── template ├── assets_embed.go ├── funcs.go ├── assets.go ├── default.tmpl └── template.go ├── Dockerfile ├── Makefile ├── .promu.yml ├── config.example.yaml ├── util ├── err.go ├── request.go └── file.go ├── api ├── api.go ├── receiver │ └── api.go └── v1 │ └── api.go ├── .circleci └── config.yml ├── deploy └── kubernetes │ └── promoter.yaml ├── go.mod ├── README.md ├── notify ├── dingtalk │ └── dingtalk.go ├── wechat │ └── wechat.go ├── notify.go └── plot.go ├── config ├── notifiers.go └── config.go ├── cmd └── promoter │ └── main.go ├── Makefile.common ├── LICENSE └── go.sum /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.3 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .tarballs/ 3 | 4 | !.build/linux-amd64/ 5 | !.build/linux-armv7/ 6 | !.build/linux-arm64/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.3 / 2022-03-05 2 | 3 | - [FEATURE] Support wechat and dingtalk message 4 | - [FEATURE] Message support alerts image render 5 | - [ENHANCEMENT] Add circle ci scripts 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | run: 3 | deadline: 5m 4 | 5 | issues: 6 | exclude-rules: 7 | - path: _test.go 8 | linters: 9 | - errcheck 10 | 11 | linters-settings: 12 | errcheck: 13 | exclude: scripts/errcheck_excludes.txt 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .idea/ 17 | 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | braces: 6 | max-spaces-inside: 1 7 | level: error 8 | brackets: 9 | max-spaces-inside: 1 10 | level: error 11 | commas: disable 12 | comments: disable 13 | comments-indentation: disable 14 | document-start: disable 15 | indentation: 16 | spaces: consistent 17 | line-length: disable 18 | -------------------------------------------------------------------------------- /scripts/errcheck_excludes.txt: -------------------------------------------------------------------------------- 1 | // Don't flag lines such as "io.Copy(ioutil.Discard, resp.Body)". 2 | io.Copy 3 | // The next two are used in HTTP handlers, any error is handled by the server itself. 4 | io.WriteString 5 | (net/http.ResponseWriter).Write 6 | // No need to check for errors on server's shutdown. 7 | (*net/http.Server).Shutdown 8 | 9 | // Never check for logger errors. 10 | (github.com/go-kit/log.Logger).Log 11 | -------------------------------------------------------------------------------- /template/assets_embed.go: -------------------------------------------------------------------------------- 1 | //go:build builtinassets 2 | // +build builtinassets 3 | 4 | package template 5 | 6 | import ( 7 | "embed" 8 | "net/http" 9 | 10 | "github.com/shurcooL/httpfs/union" 11 | ) 12 | 13 | //go:embed *.tmpl 14 | var assets embed.FS 15 | 16 | // Assets contains the project's assets. 17 | var Assets = func() http.FileSystem { 18 | return union.New(map[string]http.FileSystem{ 19 | "/templates": http.FS(assets), 20 | }) 21 | }() 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ARCH="amd64" 2 | ARG OS="linux" 3 | FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest 4 | LABEL maintainer="cnych " 5 | 6 | ARG ARCH="amd64" 7 | ARG OS="linux" 8 | COPY .build/${OS}-${ARCH}/promoter /bin/promoter 9 | COPY config.example.yaml /etc/promoter/config.yaml 10 | COPY template/default.tmpl template/default.tmpl 11 | 12 | RUN chown -R nobody:nobody etc/promoter 13 | 14 | USER nobody 15 | WORKDIR /promoter 16 | ENTRYPOINT [ "/bin/promoter" ] 17 | CMD [ "--config.file=/etc/promoter/config.yaml" ] 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | # Needs to be defined before including Makefile.common to auto-generate targets 15 | DOCKER_ARCHS ?= amd64 armv7 arm64 16 | 17 | include Makefile.common 18 | 19 | DOCKER_IMAGE_NAME ?= promoter 20 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | repository: 3 | path: github.com/cnych/promoter 4 | go: 5 | version: 1.17 6 | cgo: false 7 | build: 8 | binaries: 9 | - name: promoter 10 | path: ./cmd/promoter 11 | flags: -a -tags netgo 12 | ldflags: | 13 | -X github.com/prometheus/common/version.Version={{.Version}} 14 | -X github.com/prometheus/common/version.Revision={{.Revision}} 15 | -X github.com/prometheus/common/version.Branch={{.Branch}} 16 | -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} 17 | -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} 18 | tarball: 19 | files: 20 | - LICENSE 21 | - config.example.yaml 22 | crossbuild: 23 | platforms: 24 | - darwin 25 | - windows/amd64 26 | - windows/386 27 | - linux/amd64 28 | - linux/arm 29 | - linux/arm64 30 | -------------------------------------------------------------------------------- /config.example.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | global: 3 | prometheus_url: http://192.168.31.31:30104 4 | wechat_api_secret: 5 | wechat_api_corp_id: 6 | dingtalk_api_token: 7 | dingtalk_api_secret: 8 | 9 | s3: 10 | access_key: 11 | secret_key: 12 | endpoint: oss-cn-beijing.aliyuncs.com 13 | region: cn-beijing 14 | bucket: 15 | 16 | receivers: 17 | - name: rcv1 18 | wechat_configs: 19 | - agent_id: 20 | to_user: "@all" 21 | message_type: markdown 22 | message: '{{ template "wechat.default.message" . }}' 23 | dingtalk_configs: 24 | - message_type: markdown 25 | markdown: 26 | title: '{{ template "dingtalk.default.title" . }}' 27 | text: '{{ template "dingtalk.default.content" . }}' 28 | at: 29 | atMobiles: [ "123456" ] 30 | isAtAll: false 31 | -------------------------------------------------------------------------------- /template/funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package template 15 | 16 | import ( 17 | "bytes" 18 | ) 19 | 20 | var isMarkdownSpecial [128]bool 21 | 22 | func init() { 23 | for _, c := range "_*`" { 24 | isMarkdownSpecial[c] = true 25 | } 26 | } 27 | 28 | func markdownEscapeString(s string) string { 29 | b := make([]byte, 0, len(s)) 30 | buf := bytes.NewBuffer(b) 31 | 32 | for _, c := range s { 33 | if c < 128 && isMarkdownSpecial[c] { 34 | buf.WriteByte('\\') 35 | } 36 | buf.WriteRune(c) 37 | } 38 | return buf.String() 39 | } 40 | -------------------------------------------------------------------------------- /template/assets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build !builtinassets 15 | // +build !builtinassets 16 | 17 | package template 18 | 19 | import ( 20 | "net/http" 21 | "os" 22 | 23 | "github.com/shurcooL/httpfs/filter" 24 | "github.com/shurcooL/httpfs/union" 25 | ) 26 | 27 | // Assets contains the project's assets. 28 | var Assets = union.New(map[string]http.FileSystem{ 29 | "/templates": filter.Keep( 30 | http.Dir("../template"), 31 | func(path string, fi os.FileInfo) bool { 32 | return path == "/" || path == "/default.tmpl" 33 | }, 34 | ), 35 | }) 36 | -------------------------------------------------------------------------------- /util/err.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | ) 7 | 8 | // MultiError contains multiple errors and implements the error interface. Its 9 | // zero value is ready to use. All its methods are goroutine safe. 10 | type MultiError struct { 11 | mtx sync.Mutex 12 | errors []error 13 | } 14 | 15 | // Add adds an error to the MultiError. 16 | func (e *MultiError) Add(err error) { 17 | e.mtx.Lock() 18 | defer e.mtx.Unlock() 19 | 20 | e.errors = append(e.errors, err) 21 | } 22 | 23 | // Len returns the number of errors added to the MultiError. 24 | func (e *MultiError) Len() int { 25 | e.mtx.Lock() 26 | defer e.mtx.Unlock() 27 | 28 | return len(e.errors) 29 | } 30 | 31 | // Errors returns the errors added to the MuliError. The returned slice is a 32 | // copy of the internal slice of errors. 33 | func (e *MultiError) Errors() []error { 34 | e.mtx.Lock() 35 | defer e.mtx.Unlock() 36 | 37 | return append(make([]error, 0, len(e.errors)), e.errors...) 38 | } 39 | 40 | func (e *MultiError) Error() string { 41 | e.mtx.Lock() 42 | defer e.mtx.Unlock() 43 | 44 | es := make([]string, 0, len(e.errors)) 45 | for _, err := range e.errors { 46 | es = append(es, err.Error()) 47 | } 48 | return strings.Join(es, "; ") 49 | } 50 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | 6 | rcvapi "github.com/cnych/promoter/api/receiver" 7 | apiv1 "github.com/cnych/promoter/api/v1" 8 | "github.com/cnych/promoter/config" 9 | "github.com/cnych/promoter/template" 10 | "github.com/go-kit/log" 11 | "github.com/prometheus/common/route" 12 | ) 13 | 14 | type Options struct { 15 | Logger log.Logger 16 | Debug bool 17 | } 18 | 19 | type API struct { 20 | v1 *apiv1.API 21 | receiver *rcvapi.API 22 | } 23 | 24 | func New(opts Options) *API { 25 | l := opts.Logger 26 | if l == nil { 27 | l = log.NewNopLogger() 28 | } 29 | v1 := apiv1.New(log.With(l, "component", "apiv1")) 30 | receiverAPI := rcvapi.New(log.With(l, "component", "receiver"), opts.Debug) 31 | 32 | return &API{ 33 | v1: v1, 34 | receiver: receiverAPI, 35 | } 36 | } 37 | 38 | func (api *API) Register(r *route.Router) *http.ServeMux { 39 | api.v1.Register(r.WithPrefix("/api/v1")) 40 | api.receiver.Register(r) 41 | 42 | mux := http.NewServeMux() 43 | mux.Handle("/", r) 44 | 45 | return mux 46 | } 47 | 48 | // Update updates the config field of the API struct 49 | func (api *API) Update(conf *config.Config, tmpl *template.Template) { 50 | api.v1.Update(conf) 51 | api.receiver.Update(conf, tmpl) 52 | } 53 | -------------------------------------------------------------------------------- /template/default.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }} 2 | 3 | {{ define "default.__text_alert_list" }}{{ range . }} 4 | **{{ .Annotations.summary }}** 5 | 6 | {{ range .Images }} 7 | ![click there get alert image]({{ .Url }}) 8 | {{- end }} 9 | 10 | **description:** 11 | > {{ .Annotations.description }} 12 | 13 | **labels:** 14 | {{ range .Labels.SortedPairs }}{{ if and (ne (.Name) "severity") (ne (.Name) "summary") }}> - {{ .Name }}: {{ .Value | markdown | html }} 15 | {{ end }}{{ end }} 16 | {{ end }}{{ end }} 17 | 18 | 19 | {{ define "dingtalk.default.title" }}{{ template "__subject" . }}{{ end }} 20 | {{ define "dingtalk.default.content" }} 21 | {{ if gt (len .Alerts.Firing) 0 -}} 22 | ### {{ .Alerts.Firing | len }} Alerts Firing: 23 | {{ template "default.__text_alert_list" .Alerts.Firing }} 24 | {{ range .AtMobiles }}@{{ . }}{{ end }} 25 | {{- end }} 26 | {{ if gt (len .Alerts.Resolved) 0 -}} 27 | ### **{{ .Alerts.Resolved | len }} Alerts Resolved:** 28 | {{ template "default.__text_alert_list" .Alerts.Resolved }} 29 | {{ range .AtMobiles }}@{{ . }}{{ end }} 30 | {{- end }} 31 | {{- end }} 32 | 33 | {{ define "wechat.default.message" }} 34 | {{ if gt (len .Alerts.Firing) 0 -}} 35 | ### {{ .Alerts.Firing | len }} Alerts Firing: 36 | > {{ template "default.__text_alert_list" .Alerts.Firing }} 37 | {{- end }} 38 | {{ if gt (len .Alerts.Resolved) 0 -}} 39 | ### **{{ .Alerts.Resolved | len }} Alerts Resolved:** 40 | {{ template "default.__text_alert_list" .Alerts.Resolved }} 41 | {{- end }} 42 | {{- end }} 43 | {{ define "wechat.default.to_user" }}{{ end }} 44 | {{ define "wechat.default.to_party" }}{{ end }} 45 | {{ define "wechat.default.to_tag" }}{{ end }} 46 | {{ define "wechat.default.agent_id" }}{{ end }} -------------------------------------------------------------------------------- /util/request.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | 11 | "github.com/prometheus/common/version" 12 | ) 13 | 14 | var UserAgentHeader = fmt.Sprintf("Promoter/%s", version.Version) 15 | 16 | // RedactURL removes the URL part from an error of *url.Error type. 17 | func RedactURL(err error) error { 18 | e, ok := err.(*url.Error) 19 | if !ok { 20 | return err 21 | } 22 | e.URL = "" 23 | return e 24 | } 25 | 26 | func request(ctx context.Context, client *http.Client, method string, url string, bodyType string, body io.Reader) (*http.Response, error) { 27 | req, err := http.NewRequest(method, url, body) 28 | if err != nil { 29 | return nil, err 30 | } 31 | req.Header.Set("User-Agent", UserAgentHeader) 32 | if bodyType != "" { 33 | req.Header.Set("Content-Type", bodyType) 34 | } 35 | return client.Do(req.WithContext(ctx)) 36 | } 37 | 38 | // Get sends a GET request to the given URL 39 | func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) { 40 | return request(ctx, client, http.MethodGet, url, "", nil) 41 | } 42 | 43 | // PostJSON sends a POST request with JSON payload to the given URL. 44 | func PostJSON(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) { 45 | //b, _ := io.ReadAll(body) 46 | return post(ctx, client, url, "application/json", body) 47 | } 48 | 49 | // PostText sends a POST request with text payload to the given URL. 50 | func PostText(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) { 51 | return post(ctx, client, url, "text/plain", body) 52 | } 53 | 54 | func post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) { 55 | return request(ctx, client, http.MethodPost, url, bodyType, body) 56 | } 57 | 58 | // Drain consumes and closes the response's body to make sure that the 59 | // HTTP client can reuse existing connections. 60 | func Drain(r *http.Response) { 61 | io.Copy(ioutil.Discard, r.Body) 62 | r.Body.Close() 63 | } 64 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | orbs: 4 | prometheus: prometheus/prometheus@0.16.0 5 | go: circleci/go@1.7.1 6 | 7 | executors: 8 | golang: 9 | # Whenever the Go version is updated here 10 | # .promu.yml should also be updated. 11 | docker: 12 | - image: quay.io/prometheus/golang-builder:1.17-base 13 | 14 | jobs: 15 | test: 16 | executor: golang 17 | steps: 18 | - prometheus/setup_environment 19 | - go/load-cache: 20 | key: v1-go-mod 21 | - run: 22 | command: make 23 | environment: 24 | # By default Go uses GOMAXPROCS but a Circle CI executor has many 25 | # cores (> 30) while the CPU and RAM resources are throttled. If we 26 | # don't limit this to the number of allocated cores, the job is 27 | # likely to get OOMed and killed. 28 | GOOPTS: "-p 2" 29 | GOMAXPROCS: "2" 30 | GO111MODULE: "on" 31 | - prometheus/store_artifact: 32 | file: promoter 33 | - go/save-cache: 34 | key: v1-go-mod 35 | 36 | workflows: 37 | version: 2 38 | promoter: 39 | jobs: 40 | - test: 41 | filters: 42 | tags: 43 | only: /.*/ 44 | - prometheus/build: 45 | name: build 46 | parallelism: 3 47 | filters: 48 | tags: 49 | only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ 50 | - prometheus/publish_main: 51 | context: cnych-context 52 | requires: 53 | - test 54 | - build 55 | filters: 56 | branches: 57 | only: main 58 | quay_io_organization: '' 59 | docker_hub_organization: cnych 60 | - prometheus/publish_release: 61 | context: cnych-context 62 | requires: 63 | - test 64 | - build 65 | filters: 66 | tags: 67 | only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ 68 | branches: 69 | ignore: /.*/ 70 | quay_io_organization: '' 71 | docker_hub_organization: cnych 72 | -------------------------------------------------------------------------------- /deploy/kubernetes/promoter.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: promoter-conf 6 | namespace: kube-mon 7 | data: 8 | config.yaml: | 9 | global: 10 | prometheus_url: http:// 11 | wechat_api_secret: 12 | wechat_api_corp_id: 13 | dingtalk_api_token: 14 | dingtalk_api_secret: 15 | 16 | s3: 17 | access_key: 18 | secret_key: 19 | endpoint: oss-cn-beijing.aliyuncs.com 20 | region: cn-beijing 21 | bucket: 22 | 23 | receivers: 24 | - name: rcv1 25 | wechat_configs: 26 | - agent_id: 27 | to_user: "@all" 28 | message_type: markdown 29 | message: '{{ template "wechat.default.message" . }}' 30 | dingtalk_configs: 31 | - message_type: markdown 32 | at: 33 | atMobiles: [ "123456" ] 34 | isAtAll: false 35 | markdown: 36 | title: '{{ template "dingtalk.default.title" . }}' 37 | text: '{{ template "dingtalk.default.content" . }}' 38 | --- 39 | apiVersion: apps/v1 40 | kind: Deployment 41 | metadata: 42 | name: promoter 43 | namespace: kube-mon 44 | labels: 45 | app: promoter 46 | spec: 47 | selector: 48 | matchLabels: 49 | app: promoter 50 | template: 51 | metadata: 52 | labels: 53 | app: promoter 54 | spec: 55 | volumes: 56 | - name: config 57 | configMap: 58 | name: promoter-conf 59 | - name: timezone 60 | hostPath: 61 | path: /etc/localtime 62 | containers: 63 | - name: promoter 64 | image: cnych/promoter:main 65 | imagePullPolicy: IfNotPresent 66 | args: 67 | - "--config.file=/etc/promoter/config.yaml" 68 | ports: 69 | - containerPort: 8080 70 | volumeMounts: 71 | - mountPath: "/etc/promoter" 72 | name: config 73 | - mountPath: /etc/localtime 74 | name: timezone 75 | readOnly: true 76 | --- 77 | apiVersion: v1 78 | kind: Service 79 | metadata: 80 | name: promoter 81 | namespace: kube-mon 82 | labels: 83 | app: promoter 84 | spec: 85 | selector: 86 | app: promoter 87 | ports: 88 | - port: 8080 89 | -------------------------------------------------------------------------------- /util/file.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/aws/aws-sdk-go/aws" 14 | "github.com/aws/aws-sdk-go/aws/credentials" 15 | "github.com/aws/aws-sdk-go/aws/session" 16 | "github.com/aws/aws-sdk-go/service/s3" 17 | "github.com/globalsign/mgo/bson" 18 | ) 19 | 20 | func UploadFile(accessKey, secretKey, endpoint, bucket, region string, plot io.WriterTo) (string, error) { 21 | s := session.Must(session.NewSessionWithOptions(session.Options{ 22 | Config: aws.Config{ 23 | 24 | Endpoint: aws.String(endpoint), 25 | Region: aws.String(region), 26 | DisableSSL: aws.Bool(true), 27 | Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), 28 | }, 29 | })) 30 | if _, err := s.Config.Credentials.Get(); err != nil { 31 | return "", err 32 | } 33 | 34 | f, err := ioutil.TempFile("", "promoter-*.png") 35 | if err != nil { 36 | return "", fmt.Errorf("failed to create tmp file: %v", err) 37 | } 38 | defer func() { 39 | err = f.Close() 40 | if err != nil { 41 | panic(fmt.Errorf("failed to close tmp file: %v", err)) 42 | } 43 | err := os.Remove(f.Name()) 44 | if err != nil { 45 | panic(fmt.Errorf("failed to delete tmp file: %v", err)) 46 | } 47 | }() 48 | _, err = plot.WriteTo(f) 49 | if err != nil { 50 | return "", fmt.Errorf("failed to write plot to file: %v", err) 51 | } 52 | 53 | // get the file size and read 54 | // the file content into a buffer 55 | fileInfo, _ := f.Stat() 56 | size := fileInfo.Size() 57 | buffer := make([]byte, size) 58 | _, err = f.Seek(0, io.SeekStart) 59 | _, err = f.Read(buffer) 60 | 61 | // create a unique file name for the file 62 | tempFileName := "pictures/" + bson.NewObjectId().Hex() + "_" + strconv.FormatInt(time.Now().Unix(), 10) + ".png" 63 | 64 | _, err = s3.New(s).PutObject(&s3.PutObjectInput{ 65 | Bucket: aws.String(bucket), 66 | Key: aws.String(tempFileName), 67 | ACL: aws.String("public-read"), 68 | Body: bytes.NewReader(buffer), 69 | ContentLength: aws.Int64(size), 70 | ContentType: aws.String(http.DetectContentType(buffer)), 71 | }) 72 | if err != nil { 73 | return "", err 74 | } 75 | //http://my-oss-testing.oss-cn-beijing.aliyuncs.com/La6Z6PIeBMcCOX7cWoYz.png 76 | //return fmt.Sprintf("https://%s.s3-%s.amazonaws.com/%s", bucket, region, tempFileName), err 77 | return fmt.Sprintf("http://%s.%s/%s", bucket, endpoint, tempFileName), err 78 | } 79 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cnych/promoter 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/pkg/errors v0.9.1 7 | github.com/prometheus/common v0.32.1 8 | github.com/prometheus/prometheus v1.8.2-0.20190814100549-343d8d75fd76 9 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 10 | gonum.org/v1/plot v0.7.0 11 | ) 12 | 13 | require ( 14 | github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527 // indirect 15 | github.com/opentracing/opentracing-go v1.2.0 // indirect 16 | github.com/prometheus/client_golang v1.11.0 17 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect 18 | ) 19 | 20 | require ( 21 | github.com/aws/aws-sdk-go v1.43.7 22 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 23 | ) 24 | 25 | require ( 26 | github.com/go-kit/log v0.1.0 27 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 28 | gopkg.in/yaml.v2 v2.4.0 29 | ) 30 | 31 | require ( 32 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 33 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect 34 | github.com/beorn7/perks v1.0.1 // indirect 35 | github.com/cespare/xxhash v1.1.0 // indirect 36 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 37 | github.com/fogleman/gg v1.3.0 // indirect 38 | github.com/go-kit/kit v0.9.0 // indirect 39 | github.com/go-logfmt/logfmt v0.5.0 // indirect 40 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 41 | github.com/golang/protobuf v1.5.2 // indirect 42 | github.com/golang/snappy v0.0.3 // indirect 43 | github.com/google/go-cmp v0.5.6 // indirect 44 | github.com/jmespath/go-jmespath v0.4.0 // indirect 45 | github.com/jpillora/backoff v1.0.0 // indirect 46 | github.com/json-iterator/go v1.1.12 // indirect 47 | github.com/julienschmidt/httprouter v1.3.0 // indirect 48 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 // indirect 49 | github.com/kr/pretty v0.2.0 // indirect 50 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 52 | github.com/modern-go/reflect2 v1.0.2 // indirect 53 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect 54 | github.com/oklog/ulid v1.3.1 // indirect 55 | github.com/prometheus/client_model v0.2.0 // indirect 56 | github.com/prometheus/procfs v0.6.0 // indirect 57 | github.com/prometheus/tsdb v0.9.1 // indirect 58 | github.com/stretchr/testify v1.7.0 // indirect 59 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect 60 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 61 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 62 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 63 | golang.org/x/text v0.3.7 // indirect 64 | golang.org/x/tools v0.1.5 // indirect 65 | google.golang.org/appengine v1.6.7 // indirect 66 | google.golang.org/protobuf v1.27.1 // indirect 67 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 68 | ) 69 | -------------------------------------------------------------------------------- /template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "bytes" 5 | tmplhtml "html/template" 6 | "io/ioutil" 7 | "net/url" 8 | "path" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | tmpltext "text/template" 13 | ) 14 | 15 | type FuncMap map[string]interface{} 16 | 17 | var DefaultFuncs = FuncMap{ 18 | "toUpper": strings.ToUpper, 19 | "toLower": strings.ToLower, 20 | "title": strings.Title, 21 | // join is equal to strings.Join but inverts the argument order 22 | // for easier pipelining in templates. 23 | "join": func(sep string, s []string) string { 24 | return strings.Join(s, sep) 25 | }, 26 | "match": regexp.MatchString, 27 | "safeHtml": func(text string) tmplhtml.HTML { 28 | return tmplhtml.HTML(text) 29 | }, 30 | "reReplaceAll": func(pattern, repl, text string) string { 31 | re := regexp.MustCompile(pattern) 32 | return re.ReplaceAllString(text, repl) 33 | }, 34 | "stringSlice": func(s ...string) []string { 35 | return s 36 | }, 37 | "markdown": markdownEscapeString, 38 | } 39 | 40 | // Template bundles a text and a html template instance. 41 | type Template struct { 42 | text *tmpltext.Template 43 | html *tmplhtml.Template 44 | 45 | ExternalURL *url.URL 46 | } 47 | 48 | func FromGlobs(paths ...string) (*Template, error) { 49 | t := &Template{ 50 | text: tmpltext.New("").Option("missingkey=zero"), 51 | html: tmplhtml.New("").Option("missingkey=zero"), 52 | } 53 | 54 | t.text = t.text.Funcs(tmpltext.FuncMap(DefaultFuncs)) 55 | t.html = t.html.Funcs(tmplhtml.FuncMap(DefaultFuncs)) 56 | 57 | defaultTemplates := []string{"default.tmpl"} 58 | 59 | for _, file := range defaultTemplates { 60 | f, err := Assets.Open(path.Join("/templates", file)) 61 | if err != nil { 62 | return nil, err 63 | } 64 | defer f.Close() 65 | b, err := ioutil.ReadAll(f) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | if t.text, err = t.text.Parse(string(b)); err != nil { 71 | return nil, err 72 | } 73 | if t.html, err = t.html.Parse(string(b)); err != nil { 74 | return nil, err 75 | } 76 | } 77 | 78 | for _, tp := range paths { 79 | // ParseGlob in the template packages errors if not at least one file is 80 | // matched. We want to allow empty matches that may be populated later on. 81 | p, err := filepath.Glob(tp) 82 | if err != nil { 83 | return nil, err 84 | } 85 | if len(p) > 0 { 86 | if t.text, err = t.text.ParseGlob(tp); err != nil { 87 | return nil, err 88 | } 89 | if t.html, err = t.html.ParseGlob(tp); err != nil { 90 | return nil, err 91 | } 92 | } 93 | } 94 | return t, nil 95 | } 96 | 97 | // ExecuteTextString needs a meaningful doc comment (TODO(fabxc)). 98 | func (t *Template) ExecuteTextString(text string, data interface{}) (string, error) { 99 | if text == "" { 100 | return "", nil 101 | } 102 | tmpl, err := t.text.Clone() 103 | if err != nil { 104 | return "", err 105 | } 106 | tmpl, err = tmpl.New("").Option("missingkey=zero").Parse(text) 107 | if err != nil { 108 | return "", err 109 | } 110 | var buf bytes.Buffer 111 | err = tmpl.Execute(&buf, data) 112 | return buf.String(), err 113 | } 114 | 115 | // ExecuteHTMLString needs a meaningful doc comment (TODO(fabxc)). 116 | func (t *Template) ExecuteHTMLString(html string, data interface{}) (string, error) { 117 | if html == "" { 118 | return "", nil 119 | } 120 | tmpl, err := t.html.Clone() 121 | if err != nil { 122 | return "", err 123 | } 124 | tmpl, err = tmpl.New("").Option("missingkey=zero").Parse(html) 125 | if err != nil { 126 | return "", err 127 | } 128 | var buf bytes.Buffer 129 | err = tmpl.Execute(&buf, data) 130 | return buf.String(), err 131 | } 132 | -------------------------------------------------------------------------------- /api/receiver/api.go: -------------------------------------------------------------------------------- 1 | package receiver 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | "sync" 9 | 10 | "github.com/cnych/promoter/config" 11 | "github.com/cnych/promoter/notify" 12 | "github.com/cnych/promoter/notify/dingtalk" 13 | "github.com/cnych/promoter/notify/wechat" 14 | "github.com/cnych/promoter/template" 15 | "github.com/cnych/promoter/util" 16 | "github.com/go-kit/log" 17 | "github.com/go-kit/log/level" 18 | "github.com/prometheus/common/route" 19 | ) 20 | 21 | type API struct { 22 | mtx sync.RWMutex 23 | 24 | config *config.Config 25 | tmpl *template.Template 26 | receiverNotifiers map[string][]ReceiveNotifier 27 | logger log.Logger 28 | debug bool 29 | } 30 | 31 | type ReceiveNotifier struct { 32 | receiver *config.Receiver 33 | notifier notify.Notifier 34 | } 35 | 36 | func New(logger log.Logger, debug bool) *API { 37 | return &API{ 38 | logger: logger, 39 | debug: debug, 40 | } 41 | } 42 | 43 | func (api *API) Register(r *route.Router) { 44 | r.Post("/:name/send", api.serveReceiver) 45 | } 46 | 47 | func (api *API) serveReceiver(w http.ResponseWriter, r *http.Request) { 48 | receiverName := route.Param(r.Context(), "name") 49 | logger := log.With(api.logger, "receiver", receiverName) 50 | 51 | receiverNotifiers := api.receiverNotifiers[receiverName] 52 | if len(receiverNotifiers) == 0 { 53 | level.Warn(logger).Log("msg", "receiver not found") 54 | http.NotFound(w, r) 55 | return 56 | } 57 | 58 | var data notify.Data 59 | if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 60 | level.Error(logger).Log("msg", "Cannot decode prometheus webhook JSON request", "err", err) 61 | http.Error(w, "Bad Request", http.StatusBadRequest) 62 | return 63 | } 64 | 65 | // 生成监控图片 66 | if err := data.MakeAlertImages(logger, api.config); err != nil { 67 | level.Error(logger).Log("msg", "Cannot make alert images", "err", err) 68 | http.Error(w, "Bad Request", http.StatusBadRequest) 69 | return 70 | } 71 | 72 | errs := &util.MultiError{} 73 | 74 | for _, rn := range receiverNotifiers { 75 | if _, err := rn.notifier.Notify(context.Background(), &data); err != nil { 76 | errs.Add(err) 77 | } 78 | } 79 | 80 | if errs.Len() > 0 { 81 | level.Error(logger).Log("msg", "Send receiver notify failed", "err", errs) 82 | http.Error(w, "Bad Request", http.StatusBadRequest) 83 | return 84 | } 85 | 86 | io.WriteString(w, "OK") 87 | } 88 | 89 | func (api *API) Update(conf *config.Config, tmpl *template.Template) { 90 | api.mtx.Lock() 91 | defer api.mtx.Unlock() 92 | 93 | api.config = conf 94 | api.tmpl = tmpl 95 | 96 | // 将 Receivers 映射成 map,获取每个接收器的 notifier 97 | var receiverNotifier = make(map[string][]ReceiveNotifier) 98 | for _, rcv := range api.config.Receivers { 99 | var receiverNotifiers []ReceiveNotifier 100 | for _, dtc := range rcv.DingtalkConfigs { 101 | notifier, err := dingtalk.New(dtc, api.tmpl, api.logger) 102 | if err != nil { 103 | level.Error(api.logger).Log("msg", "Init dingtalk notifier", "err", err) 104 | continue 105 | } 106 | receiverNotifiers = append(receiverNotifiers, ReceiveNotifier{ 107 | receiver: rcv, 108 | notifier: notifier, 109 | }) 110 | } 111 | for _, wcc := range rcv.WechatConfigs { 112 | notifier, err := wechat.New(wcc, api.tmpl, api.logger) 113 | if err != nil { 114 | level.Error(api.logger).Log("msg", "Init wechat notifier", "err", err) 115 | continue 116 | } 117 | receiverNotifiers = append(receiverNotifiers, ReceiveNotifier{ 118 | receiver: rcv, 119 | notifier: notifier, 120 | }) 121 | } 122 | receiverNotifier[rcv.Name] = receiverNotifiers 123 | } 124 | api.receiverNotifiers = receiverNotifier 125 | } 126 | -------------------------------------------------------------------------------- /api/v1/api.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "sync" 7 | "time" 8 | 9 | "github.com/cnych/promoter/config" 10 | "github.com/go-kit/log" 11 | "github.com/go-kit/log/level" 12 | "github.com/prometheus/common/route" 13 | "github.com/prometheus/common/version" 14 | ) 15 | 16 | var corsHeaders = map[string]string{ 17 | "Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, Origin", 18 | "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS", 19 | "Access-Control-Allow-Origin": "*", 20 | "Access-Control-Expose-Headers": "Date", 21 | "Cache-Control": "no-cache, no-store, must-revalidate", 22 | } 23 | 24 | // Enables cross-site script calls. 25 | func setCORS(w http.ResponseWriter) { 26 | for h, v := range corsHeaders { 27 | w.Header().Set(h, v) 28 | } 29 | } 30 | 31 | type API struct { 32 | logger log.Logger 33 | config *config.Config 34 | 35 | uptime time.Time 36 | mtx sync.RWMutex 37 | } 38 | 39 | func New(l log.Logger) *API { 40 | if l == nil { 41 | l = log.NewNopLogger() 42 | } 43 | return &API{ 44 | logger: l, 45 | uptime: time.Now(), 46 | } 47 | } 48 | 49 | func (api *API) Register(r *route.Router) { 50 | wrap := func(f http.HandlerFunc) http.HandlerFunc { 51 | return func(w http.ResponseWriter, r *http.Request) { 52 | setCORS(w) 53 | f(w, r) 54 | } 55 | } 56 | 57 | r.Options("/*path", wrap(func(w http.ResponseWriter, r *http.Request) {})) 58 | 59 | r.Get("/status", wrap(api.status)) 60 | r.Get("/receivers", wrap(api.receivers)) 61 | 62 | //todo,将报警数据保存 63 | //r.Get("/alerts", wrap(api.listAlerts)) 64 | //r.Post("/alerts", wrap(api.addAlerts)) 65 | } 66 | 67 | func (api *API) Update(conf *config.Config) { 68 | api.mtx.Lock() 69 | defer api.mtx.Unlock() 70 | 71 | api.config = conf 72 | } 73 | 74 | func (api *API) receivers(w http.ResponseWriter, req *http.Request) { 75 | api.mtx.RLock() 76 | defer api.mtx.RUnlock() 77 | 78 | receivers := make([]string, 0, len(api.config.Receivers)) 79 | for _, r := range api.config.Receivers { 80 | receivers = append(receivers, r.Name) 81 | } 82 | 83 | api.respond(w, receivers) 84 | } 85 | 86 | func (api *API) status(w http.ResponseWriter, req *http.Request) { 87 | api.mtx.RLock() 88 | 89 | var status = struct { 90 | ConfigYAML string `json:"configYAML"` 91 | ConfigJSON *config.Config `json:"configJSON"` 92 | VersionInfo map[string]string `json:"versionInfo"` 93 | Uptime time.Time `json:"uptime"` 94 | }{ 95 | ConfigYAML: api.config.String(), 96 | ConfigJSON: api.config, 97 | VersionInfo: map[string]string{ 98 | "version": version.Version, 99 | "revision": version.Revision, 100 | "branch": version.Branch, 101 | "buildUser": version.BuildUser, 102 | "buildDate": version.BuildDate, 103 | "goVersion": version.GoVersion, 104 | }, 105 | Uptime: api.uptime, 106 | } 107 | 108 | api.mtx.RUnlock() 109 | 110 | api.respond(w, status) 111 | } 112 | 113 | func (api *API) respond(w http.ResponseWriter, data interface{}) { 114 | statusMessage := statusSuccess 115 | b, err := json.Marshal(&response{ 116 | Status: statusMessage, 117 | Data: data, 118 | }) 119 | if err != nil { 120 | level.Error(api.logger).Log("msg", "error marshaling json response", "err", err) 121 | http.Error(w, err.Error(), http.StatusInternalServerError) 122 | return 123 | } 124 | 125 | w.Header().Set("Content-Type", "application/json") 126 | w.WriteHeader(http.StatusOK) 127 | if n, err := w.Write(b); err != nil { 128 | level.Error(api.logger).Log("msg", "error writing response", "bytesWritten", n, "err", err) 129 | } 130 | } 131 | 132 | type status string 133 | 134 | const ( 135 | statusSuccess status = "success" 136 | ) 137 | 138 | type errorType string 139 | 140 | type response struct { 141 | Status status `json:"status"` 142 | Data interface{} `json:"data,omitempty"` 143 | ErrorType errorType `json:"errorType,omitempty"` 144 | Error string `json:"error,omitempty"` 145 | } 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Promoter ![CircleCI](https://circleci.com/gh/cnych/promoter/tree/main.svg?style=shield) 2 | 3 | Promoter 是一个用于 AlertManager 报警通知的 Webhooks 实现,目前支持`钉钉`和`企业微信`,支持在消息通知中展示实时报警图表。 4 | 5 | ![](https://bxdc-static.oss-cn-beijing.aliyuncs.com/images/20220226181006.png) 6 | 7 | 8 | ## 安装 9 | 10 | 该项目使用 Go 语言编写,有多种方式来安装 Promoter。 11 | 12 | ### 编译二进制 13 | 14 | 直接 clone 项目手动构建: 15 | ```shell 16 | $ git clone https://github.com/cnych/promoter.git 17 | $ cd promoter 18 | $ go build -a -o promoter cmd/promoter/main.go 19 | $ ./promoter --config.file= 20 | ``` 21 | 22 | ### Docker 镜像 23 | 24 | Promoter 镜像上传到了 Docker Hub,你可以尝试使用下面的命令来启动服务: 25 | 26 | ```shell 27 | $ docker run --name promoter -d -p 8080:8080 cnych/promoter:main 28 | ``` 29 | 30 | ## 配置 31 | 32 | 下面的配置基本上覆盖了 Promoter 使用的配置: 33 | 34 | ```yaml 35 | global: 36 | prometheus_url: http://192.168.31.31:30104 37 | wechat_api_secret: 38 | wechat_api_corp_id: 39 | dingtalk_api_token: 40 | dingtalk_api_secret: 41 | 42 | s3: 43 | access_key: 44 | secret_key: 45 | endpoint: oss-cn-beijing.aliyuncs.com 46 | region: cn-beijing 47 | bucket: 48 | 49 | receivers: 50 | - name: rcv1 51 | wechat_configs: 52 | - agent_id: 53 | to_user: "@all" 54 | message_type: markdown 55 | message: '{{ template "wechat.default.message" . }}' 56 | dingtalk_configs: 57 | - message_type: markdown 58 | markdown: 59 | title: '{{ template "dingtalk.default.title" . }}' 60 | text: '{{ template "dingtalk.default.content" . }}' 61 | at: 62 | atMobiles: [ "123456" ] 63 | isAtAll: false 64 | ``` 65 | 66 | 在 global 下面可以配置全局属性,比如企业微信或者钉钉的密钥,S3 下面是一个对象存储(阿里云 OSS 也可以)配置,用来保存监控图标生成的图片。 67 | 68 | `receivers` 下面是配置的各种消息的接收器,可以在一个接收器中同时配置企业微信和钉钉,支持 `text` 和 `markdown` 两种格式,其中的 `name` 非常中, 69 | 比如这里名称叫`rcv1`,那么该接收器的 Webhook 地址为:`http:///rcv1/send`,在 AlertManager Webhook 中需要配置该地址。 70 | 71 | > 需要注意企业微信的 Markdown 格式不支持直接展示图片 72 | 73 | ## 模板 74 | 75 | 默认模板位于 `template/default.tmpl`,可以根据自己需求定制: 76 | 77 | ```tmpl 78 | {{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }} 79 | 80 | {{ define "default.__text_alert_list" }}{{ range . }} 81 | **{{ .Annotations.summary }}** 82 | 83 | {{ range .Images }} 84 | ![click there get alert image]({{ .Url }}) 85 | {{- end }} 86 | 87 | **description:** 88 | > {{ .Annotations.description }} 89 | 90 | **labels:** 91 | {{ range .Labels.SortedPairs }}{{ if and (ne (.Name) "severity") (ne (.Name) "summary") }}> - {{ .Name }}: {{ .Value | markdown | html }} 92 | {{ end }}{{ end }} 93 | {{ end }}{{ end }} 94 | 95 | {{ define "dingtalk.default.title" }}{{ template "__subject" . }}{{ end }} 96 | {{ define "dingtalk.default.content" }} 97 | {{ if gt (len .Alerts.Firing) 0 -}} 98 | ### {{ .Alerts.Firing | len }} Alerts Firing: 99 | {{ template "default.__text_alert_list" .Alerts.Firing }} 100 | {{ range .AtMobiles }}@{{ . }}{{ end }} 101 | {{- end }} 102 | {{ if gt (len .Alerts.Resolved) 0 -}} 103 | ### **{{ .Alerts.Resolved | len }} Alerts Resolved:** 104 | {{ template "default.__text_alert_list" .Alerts.Resolved }} 105 | {{ range .AtMobiles }}@{{ . }}{{ end }} 106 | {{- end }} 107 | {{- end }} 108 | 109 | {{ define "wechat.default.message" }} 110 | {{ if gt (len .Alerts.Firing) 0 -}} 111 | ### {{ .Alerts.Firing | len }} Alerts Firing: 112 | > {{ template "default.__text_alert_list" .Alerts.Firing }} 113 | {{- end }} 114 | {{ if gt (len .Alerts.Resolved) 0 -}} 115 | ### **{{ .Alerts.Resolved | len }} Alerts Resolved:** 116 | {{ template "default.__text_alert_list" .Alerts.Resolved }} 117 | {{- end }} 118 | {{- end }} 119 | {{ define "wechat.default.to_user" }}{{ end }} 120 | {{ define "wechat.default.to_party" }}{{ end }} 121 | {{ define "wechat.default.to_tag" }}{{ end }} 122 | {{ define "wechat.default.agent_id" }}{{ end }} 123 | ``` 124 | -------------------------------------------------------------------------------- /notify/dingtalk/dingtalk.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/hmac" 7 | "crypto/sha256" 8 | "encoding/base64" 9 | "encoding/json" 10 | "fmt" 11 | "io/ioutil" 12 | "net/http" 13 | "strconv" 14 | "time" 15 | 16 | "github.com/cnych/promoter/config" 17 | "github.com/cnych/promoter/notify" 18 | "github.com/cnych/promoter/template" 19 | "github.com/cnych/promoter/util" 20 | "github.com/go-kit/log" 21 | "github.com/go-kit/log/level" 22 | "github.com/pkg/errors" 23 | commoncfg "github.com/prometheus/common/config" 24 | ) 25 | 26 | type Notifier struct { 27 | tmpl *template.Template 28 | conf *config.DingtalkConfig 29 | client *http.Client 30 | logger log.Logger 31 | } 32 | 33 | func New(conf *config.DingtalkConfig, tmpl *template.Template, l log.Logger, httpOpts ...commoncfg.HTTPClientOption) (notify.Notifier, error) { 34 | client, err := commoncfg.NewClientFromConfig(*conf.HTTPConfig, "dingtalk", httpOpts...) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return &Notifier{conf: conf, tmpl: tmpl, logger: l, client: client}, nil 39 | } 40 | 41 | func (n *Notifier) Notify(ctx context.Context, data *notify.Data) (bool, error) { 42 | var err error 43 | tmpl := notify.TmplText(n.tmpl, data, &err) 44 | if err != nil { 45 | return false, err 46 | } 47 | 48 | var at dingtalkMessageAt 49 | if n.conf.At != nil { 50 | at = dingtalkMessageAt{ 51 | AtMobiles: n.conf.At.AtMobiles, 52 | IsAtAll: n.conf.At.IsAtAll, 53 | } 54 | } 55 | msg := &dingtalkMessage{ 56 | Type: n.conf.MessageType, 57 | At: &at, 58 | } 59 | if msg.Type == "markdown" { 60 | msg.Markdown = &dingtalkMessageMarkdown{ 61 | Title: tmpl(n.conf.Markdown.Title), 62 | Text: tmpl(n.conf.Markdown.Text), 63 | } 64 | } else { 65 | if n.conf.Text != nil { 66 | msg.Text = &dingtalkMessageText{ 67 | Title: tmpl(n.conf.Text.Title), 68 | Content: tmpl(n.conf.Text.Content), 69 | } 70 | } 71 | } 72 | 73 | var buf bytes.Buffer 74 | if err := json.NewEncoder(&buf).Encode(msg); err != nil { 75 | return false, err 76 | } 77 | 78 | postMessageURL := n.conf.APIURL.Copy() 79 | q := postMessageURL.Query() 80 | 81 | if n.conf.APISecret != "" { 82 | // 如果配置了 Secret,则需要签名 83 | timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10) 84 | strToSign := []byte(timestamp + "\n" + string(n.conf.APISecret)) 85 | 86 | mac := hmac.New(sha256.New, []byte(n.conf.APISecret)) 87 | mac.Write(strToSign) 88 | signature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) 89 | 90 | q.Set("timestamp", timestamp) 91 | q.Set("sign", signature) 92 | } 93 | 94 | q.Set("access_token", string(n.conf.APIToken)) 95 | postMessageURL.RawQuery = q.Encode() 96 | 97 | resp, err := util.PostJSON(ctx, n.client, postMessageURL.String(), &buf) 98 | if err != nil { 99 | return true, util.RedactURL(err) 100 | } 101 | defer util.Drain(resp) 102 | 103 | if resp.StatusCode != 200 { 104 | return true, fmt.Errorf("unexpected status code %v", resp.StatusCode) 105 | } 106 | 107 | body, err := ioutil.ReadAll(resp.Body) 108 | if err != nil { 109 | return true, err 110 | } 111 | level.Debug(n.logger).Log("response", string(body)) 112 | 113 | var dtResp dingtalkResponse 114 | if err := json.Unmarshal(body, &dtResp); err != nil { 115 | return true, err 116 | } 117 | if dtResp.Code != 0 { 118 | return false, errors.New(dtResp.Message) 119 | } 120 | return false, nil 121 | } 122 | 123 | type dingtalkResponse struct { 124 | Message string `json:"errmsg"` 125 | Code int `json:"errcode"` 126 | } 127 | 128 | type dingtalkMessage struct { 129 | Type string `json:"msgtype,omitempty"` 130 | Text *dingtalkMessageText `json:"text,omitempty"` 131 | Markdown *dingtalkMessageMarkdown `json:"markdown,omitempty"` 132 | At *dingtalkMessageAt `json:"at,omitempty"` 133 | } 134 | 135 | type dingtalkMessageText struct { 136 | Title string `json:"title"` 137 | Content string `json:"content"` 138 | } 139 | 140 | type dingtalkMessageMarkdown struct { 141 | Title string `json:"title"` 142 | Text string `json:"text"` 143 | } 144 | 145 | type dingtalkMessageAt struct { 146 | AtMobiles []string `json:"atMobiles,omitempty"` 147 | IsAtAll bool `json:"isAtAll,omitempty"` 148 | } 149 | -------------------------------------------------------------------------------- /config/notifiers.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/pkg/errors" 7 | commoncfg "github.com/prometheus/common/config" 8 | ) 9 | 10 | var ( 11 | // DefaultWechatConfig defines default values for wechat configurations. 12 | DefaultWechatConfig = WechatConfig{ 13 | Message: `{{ template "wechat.default.message" . }}`, 14 | ToUser: `{{ template "wechat.default.to_user" . }}`, 15 | ToParty: `{{ template "wechat.default.to_party" . }}`, 16 | ToTag: `{{ template "wechat.default.to_tag" . }}`, 17 | AgentID: `{{ template "wechat.default.agent_id" . }}`, 18 | } 19 | // DefaultDingtalkConfig ...... 20 | DefaultDingtalkConfig = DingtalkConfig{ 21 | Markdown: &DingtalkMarkdown{ 22 | Title: `{{ template "dingtalk.default.title" . }}`, 23 | Text: `{{ template "dingtalk.default.content" . }}`, 24 | }, 25 | } 26 | ) 27 | 28 | // WechatConfig configures notifications via Wechat. 29 | type WechatConfig struct { 30 | //NotifierConfig `yaml:",inline" json:",inline"` 31 | HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` 32 | 33 | APISecret Secret `yaml:"api_secret,omitempty" json:"api_secret,omitempty"` 34 | CorpID Secret `yaml:"corp_id,omitempty" json:"corp_id,omitempty"` 35 | Message string `yaml:"message,omitempty" json:"message,omitempty"` 36 | TemplateCard *WechatTemplateCard `yaml:"template_card,omitempty" json:"template_card,omitempty"` 37 | APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"` 38 | ToUser string `yaml:"to_user,omitempty" json:"to_user,omitempty"` 39 | ToParty string `yaml:"to_party,omitempty" json:"to_party,omitempty"` 40 | ToTag string `yaml:"to_tag,omitempty" json:"to_tag,omitempty"` 41 | AgentID string `yaml:"agent_id,omitempty" json:"agent_id,omitempty"` 42 | MessageType string `yaml:"message_type,omitempty" json:"message_type,omitempty"` 43 | } 44 | 45 | type WechatTemplateCard struct { 46 | Title string `yaml:"title" json:"title"` 47 | Description string `yaml:"desc" json:"desc"` 48 | ImageURL string `yaml:"image_url" json:"image_url"` 49 | } 50 | 51 | const wechatValidTypesRe = `^(text|markdown|template_card|news)$` 52 | 53 | var wechatTypeMatcher = regexp.MustCompile(wechatValidTypesRe) 54 | 55 | // UnmarshalYAML implements the yaml.Unmarshaler interface. 56 | func (c *WechatConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 57 | *c = DefaultWechatConfig 58 | type plain WechatConfig 59 | if err := unmarshal((*plain)(c)); err != nil { 60 | return err 61 | } 62 | 63 | if c.MessageType == "" { 64 | c.MessageType = "text" 65 | } 66 | 67 | if !wechatTypeMatcher.MatchString(c.MessageType) { 68 | return errors.Errorf("WeChat message type %q does not match valid options %s", c.MessageType, wechatValidTypesRe) 69 | } 70 | 71 | return nil 72 | } 73 | 74 | type DingtalkConfig struct { 75 | HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` 76 | 77 | APISecret Secret `yaml:"api_secret,omitempty" json:"api_secret,omitempty"` 78 | APIToken Secret `yaml:"api_token,omitempty" json:"api_token,omitempty"` 79 | APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"` 80 | 81 | Text *DingtalkText `yaml:"text,omitempty" json:"text,omitempty"` 82 | Markdown *DingtalkMarkdown `yaml:"markdown,omitempty" json:"markdown,omitempty"` 83 | At *DingtalkAt `yaml:"at,omitempty" json:"at,omitempty"` 84 | MessageType string `yaml:"message_type,omitempty" json:"message_type,omitempty"` 85 | } 86 | 87 | const dingtalkValidTypesRe = `^(text|markdown)$` 88 | 89 | var dingtalkTypeMatcher = regexp.MustCompile(dingtalkValidTypesRe) 90 | 91 | // UnmarshalYAML implements the yaml.Unmarshaler interface. 92 | func (c *DingtalkConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 93 | *c = DefaultDingtalkConfig 94 | type plain DingtalkConfig 95 | if err := unmarshal((*plain)(c)); err != nil { 96 | return err 97 | } 98 | 99 | if c.MessageType == "" { 100 | c.MessageType = "text" 101 | } 102 | 103 | if !dingtalkTypeMatcher.MatchString(c.MessageType) { 104 | return errors.Errorf("Dingtalk message type %q does not match valid options %s", c.MessageType, dingtalkValidTypesRe) 105 | } 106 | 107 | return nil 108 | } 109 | 110 | type DingtalkText struct { 111 | Title string `yaml:"title" json:"title"` 112 | Content string `yaml:"content" json:"content"` 113 | } 114 | 115 | type DingtalkMarkdown struct { 116 | Title string `yaml:"title" json:"title"` 117 | Text string `yaml:"text" json:"text"` 118 | } 119 | 120 | type DingtalkAt struct { 121 | AtMobiles []string `yaml:"atMobiles" json:"atMobiles,omitempty"` 122 | IsAtAll bool `yaml:"isAtAll" json:"isAtAll,omitempty"` 123 | } 124 | -------------------------------------------------------------------------------- /cmd/promoter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "os/signal" 10 | "runtime" 11 | "strings" 12 | "syscall" 13 | 14 | "github.com/cnych/promoter/api" 15 | "github.com/cnych/promoter/config" 16 | "github.com/cnych/promoter/template" 17 | "github.com/go-kit/log" 18 | "github.com/go-kit/log/level" 19 | "github.com/pkg/errors" 20 | "github.com/prometheus/common/promlog" 21 | promlogflag "github.com/prometheus/common/promlog/flag" 22 | "github.com/prometheus/common/route" 23 | "github.com/prometheus/common/version" 24 | "gopkg.in/alecthomas/kingpin.v2" 25 | ) 26 | 27 | var ( 28 | promlogConfig = promlog.Config{} 29 | term = make(chan os.Signal, 1) 30 | ) 31 | 32 | func main() { 33 | os.Exit(run()) 34 | } 35 | 36 | func run() int { 37 | if os.Getenv("DEBUG") != "" { 38 | runtime.SetBlockProfileRate(20) 39 | runtime.SetMutexProfileFraction(20) 40 | } 41 | 42 | var ( 43 | configFile = kingpin.Flag("config.file", "Promoter configuration file.").Default("config.yaml").ExistingFile() 44 | debug = kingpin.Flag("web.debug", "Dump request data").Default("false").Bool() 45 | externalURL = kingpin.Flag("web.external-url", "The URL under which Promoter is externally reachable (for example, if Promoter is served via a reverse proxy). Used for generating relative and absolute links back to Promoter itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Promoter. If omitted, relevant URL components will be derived automatically.").String() 46 | listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for the web interface and API.").Default(":8080").String() 47 | ) 48 | 49 | promlogflag.AddFlags(kingpin.CommandLine, &promlogConfig) 50 | 51 | kingpin.CommandLine.UsageWriter(os.Stdout) 52 | kingpin.Version(version.Print("promoter")) 53 | kingpin.CommandLine.GetFlag("help").Short('h') 54 | kingpin.Parse() 55 | 56 | logger := promlog.New(&promlogConfig) 57 | 58 | level.Info(logger).Log("msg", "Staring Promoter", "version", version.Info()) 59 | level.Info(logger).Log("build_context", version.BuildContext()) 60 | 61 | // 加载配置文件和模板 62 | conf, err := loadConfiguration(logger, *configFile) 63 | if err != nil { 64 | return 1 65 | } 66 | amURL, err := extURL(logger, os.Hostname, *listenAddress, *externalURL) 67 | if err != nil { 68 | level.Error(logger).Log("msg", "failed to determine external URL", "err", err) 69 | return 1 70 | } 71 | level.Debug(logger).Log("msg", "parse promoter external url", "url", amURL.String()) 72 | tmpl, err := loadTemplate(logger, conf, amURL) 73 | if err != nil { 74 | return 1 75 | } 76 | 77 | api := api.New(api.Options{ 78 | Logger: logger, 79 | Debug: *debug, 80 | }) 81 | api.Update(conf, tmpl) // 更新配置对象 82 | 83 | mux := api.Register(route.New()) // 注册路由 84 | srv := http.Server{Addr: *listenAddress, Handler: mux} 85 | srvc := make(chan struct{}) 86 | 87 | go func() { 88 | level.Info(logger).Log("msg", "Listening", "address", *listenAddress) 89 | if err := srv.ListenAndServe(); err != http.ErrServerClosed { 90 | level.Error(logger).Log("msg", "Listen error", "err", err) 91 | close(srvc) 92 | } 93 | defer func() { 94 | if err := srv.Close(); err != nil { 95 | level.Error(logger).Log("msg", "Error on closing the server", "err", err) 96 | } 97 | }() 98 | }() 99 | 100 | signal.Notify(term, os.Interrupt, syscall.SIGTERM) 101 | 102 | for { 103 | select { 104 | case <-term: 105 | level.Info(logger).Log("msg", "Received SIGTERM, exiting gracefully...") 106 | return 0 107 | case <-srvc: 108 | return 1 109 | } 110 | } 111 | } 112 | 113 | func loadTemplate(logger log.Logger, conf *config.Config, externalURL *url.URL) (*template.Template, error) { 114 | tmplLogger := log.With(logger, "component", "template") 115 | 116 | tmpl, err := template.FromGlobs(conf.Templates...) 117 | if err != nil { 118 | level.Error(tmplLogger).Log("msg", errors.Wrap(err, "failed to parse templates")) 119 | return nil, err 120 | } 121 | tmpl.ExternalURL = externalURL 122 | return tmpl, nil 123 | } 124 | 125 | func loadConfiguration(logger log.Logger, configFilePath string) (*config.Config, error) { 126 | configLogger := log.With(logger, "component", "configuration") 127 | level.Info(configLogger).Log("msg", "Loading configuration file", "file", configFilePath) 128 | 129 | // 加载配置文件和模板 130 | conf, err := config.LoadFile(configFilePath) 131 | if err != nil { 132 | level.Error(configLogger).Log( 133 | "msg", "Loading configuration file failed", 134 | "file", configFilePath, 135 | "err", err) 136 | return nil, err 137 | } 138 | level.Info(configLogger).Log("msg", "Completed loading of configuration file", "file", configFilePath) 139 | return conf, nil 140 | } 141 | 142 | func extURL(logger log.Logger, hostnamef func() (string, error), listen, external string) (*url.URL, error) { 143 | if external == "" { 144 | hostname, err := hostnamef() 145 | if err != nil { 146 | return nil, err 147 | } 148 | _, port, err := net.SplitHostPort(listen) 149 | if err != nil { 150 | return nil, err 151 | } 152 | if port == "" { 153 | level.Warn(logger).Log("msg", "no port found for listen address", "address", listen) 154 | } 155 | 156 | external = fmt.Sprintf("http://%s:%s/", hostname, port) 157 | } 158 | 159 | u, err := url.Parse(external) 160 | if err != nil { 161 | return nil, err 162 | } 163 | if u.Scheme != "http" && u.Scheme != "https" { 164 | return nil, errors.Errorf("%q: invalid %q scheme, only 'http' and 'https' are supported", u.String(), u.Scheme) 165 | } 166 | 167 | ppref := strings.TrimRight(u.Path, "/") 168 | if ppref != "" && !strings.HasPrefix(ppref, "/") { 169 | ppref = "/" + ppref 170 | } 171 | u.Path = ppref 172 | 173 | return u, nil 174 | } 175 | -------------------------------------------------------------------------------- /notify/wechat/wechat.go: -------------------------------------------------------------------------------- 1 | package wechat 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "time" 12 | 13 | "github.com/cnych/promoter/config" 14 | "github.com/cnych/promoter/notify" 15 | "github.com/cnych/promoter/template" 16 | "github.com/cnych/promoter/util" 17 | "github.com/go-kit/log" 18 | "github.com/go-kit/log/level" 19 | "github.com/pkg/errors" 20 | commoncfg "github.com/prometheus/common/config" 21 | ) 22 | 23 | type Notifier struct { 24 | tmpl *template.Template 25 | conf *config.WechatConfig 26 | client *http.Client 27 | logger log.Logger 28 | accessToken string 29 | accessTokenAt time.Time 30 | } 31 | 32 | type token struct { 33 | AccessToken string `json:"access_token"` 34 | } 35 | 36 | // New 返回一个新的 Wechat notifier 对象 37 | func New(c *config.WechatConfig, t *template.Template, l log.Logger, httpOpts ...commoncfg.HTTPClientOption) (notify.Notifier, error) { 38 | client, err := commoncfg.NewClientFromConfig(*c.HTTPConfig, "wechat", httpOpts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return &Notifier{conf: c, tmpl: t, logger: l, client: client}, nil 43 | } 44 | 45 | func (n *Notifier) Notify(ctx context.Context, data *notify.Data) (bool, error) { 46 | var err error 47 | tmpl := notify.TmplText(n.tmpl, data, &err) 48 | if err != nil { 49 | return false, err 50 | } 51 | 52 | // 超过2小时刷新 AccessToken 53 | if n.accessToken == "" || time.Since(n.accessTokenAt) > 2*time.Hour { 54 | parameters := url.Values{} 55 | parameters.Add("corpsecret", string(n.conf.APISecret)) 56 | parameters.Add("corpid", string(n.conf.CorpID)) 57 | 58 | u := n.conf.APIURL.Copy() 59 | u.Path += "gettoken" 60 | u.RawQuery = parameters.Encode() 61 | 62 | resp, err := util.Get(ctx, n.client, u.String()) 63 | if err != nil { 64 | return true, util.RedactURL(err) 65 | } 66 | defer util.Drain(resp) 67 | 68 | var wechatToken token 69 | if err := json.NewDecoder(resp.Body).Decode(&wechatToken); err != nil { 70 | return false, err 71 | } 72 | 73 | if wechatToken.AccessToken == "" { 74 | return false, fmt.Errorf("invalid APISecret for CorpID: %s", n.conf.CorpID) 75 | } 76 | 77 | // 缓存 token 78 | n.accessToken = wechatToken.AccessToken 79 | n.accessTokenAt = time.Now() 80 | } 81 | 82 | msg := &weChatMessage{ 83 | ToUser: tmpl(n.conf.ToUser), 84 | ToParty: tmpl(n.conf.ToParty), 85 | Totag: tmpl(n.conf.ToTag), 86 | AgentID: tmpl(n.conf.AgentID), 87 | Type: n.conf.MessageType, 88 | Safe: "0", 89 | } 90 | if msg.Type == "markdown" { 91 | msg.Markdown = weChatMessageContent{ 92 | Content: tmpl(n.conf.Message), 93 | } 94 | } else if msg.Type == "template_card" { 95 | msg.TemplateCard = weChatMessageTemplateCard{ 96 | CardType: "news_notice", 97 | MainTitle: weChatMessageTemplateMainTitle{ 98 | Title: tmpl(n.conf.TemplateCard.Title), 99 | Desc: tmpl(n.conf.TemplateCard.Description), 100 | }, 101 | ImageTextArea: wechatMessageTemplateImage{ 102 | Type: 1, 103 | URL: n.tmpl.ExternalURL.String(), 104 | Title: tmpl(n.conf.TemplateCard.Title), 105 | Desc: tmpl(n.conf.TemplateCard.Description), 106 | ImageURL: tmpl(n.conf.TemplateCard.ImageURL), 107 | }, 108 | } 109 | } else { 110 | msg.Text = weChatMessageContent{ 111 | Content: tmpl(n.conf.Message), 112 | } 113 | } 114 | 115 | var buf bytes.Buffer 116 | if err := json.NewEncoder(&buf).Encode(msg); err != nil { 117 | return false, err 118 | } 119 | 120 | postMessageURL := n.conf.APIURL.Copy() 121 | postMessageURL.Path += "message/send" 122 | q := postMessageURL.Query() 123 | q.Set("access_token", n.accessToken) 124 | postMessageURL.RawQuery = q.Encode() 125 | 126 | resp, err := util.PostJSON(ctx, n.client, postMessageURL.String(), &buf) 127 | if err != nil { 128 | return false, util.RedactURL(err) 129 | } 130 | defer util.Drain(resp) 131 | 132 | if resp.StatusCode != 200 { 133 | return true, fmt.Errorf("unexpected status code %v", resp.StatusCode) 134 | } 135 | 136 | body, err := ioutil.ReadAll(resp.Body) 137 | level.Debug(n.logger).Log("response", string(body)) 138 | if err != nil { 139 | return false, err 140 | } 141 | 142 | var weResp weChatResponse 143 | if err := json.Unmarshal(body, &weResp); err != nil { 144 | return false, err 145 | } 146 | 147 | // https://work.weixin.qq.com/api/doc#10649 148 | if weResp.Code == 0 { 149 | return false, nil 150 | } 151 | 152 | // AccessToken is expired 153 | if weResp.Code == 42001 { 154 | n.accessToken = "" 155 | return true, errors.New(weResp.Error) 156 | } 157 | 158 | return false, errors.New(weResp.Error) 159 | } 160 | 161 | type weChatMessage struct { 162 | Text weChatMessageContent `json:"text,omitempty"` 163 | ToUser string `json:"touser,omitempty"` 164 | ToParty string `json:"toparty,omitempty"` 165 | Totag string `json:"totag,omitempty"` 166 | AgentID string `json:"agentid,omitempty"` 167 | Safe string `json:"safe,omitempty"` 168 | Type string `json:"msgtype,omitempty"` 169 | Markdown weChatMessageContent `json:"markdown,omitempty"` 170 | TemplateCard weChatMessageTemplateCard `json:"template_card,omitempty"` 171 | News weChatMessageNews `json:"news,omitempty"` 172 | } 173 | 174 | type weChatMessageContent struct { 175 | Content string `json:"content"` 176 | } 177 | 178 | type weChatMessageNews struct { 179 | } 180 | 181 | type weChatMessageTemplateCard struct { 182 | CardType string `json:"card_type"` 183 | MainTitle weChatMessageTemplateMainTitle `json:"main_title"` 184 | ImageTextArea wechatMessageTemplateImage `json:"image_text_area"` 185 | } 186 | 187 | type weChatMessageTemplateMainTitle struct { 188 | Title string `json:"title"` 189 | Desc string `json:"desc"` 190 | } 191 | 192 | type wechatMessageTemplateImage struct { 193 | Type int `json:"type"` 194 | URL string `json:"url"` 195 | Title string `json:"title"` 196 | Desc string `json:"desc"` 197 | ImageURL string `json:"image_url"` 198 | } 199 | 200 | type weChatResponse struct { 201 | Code int `json:"code"` 202 | Error string `json:"error"` 203 | } 204 | -------------------------------------------------------------------------------- /notify/notify.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | "sort" 8 | "time" 9 | 10 | "github.com/cnych/promoter/config" 11 | "github.com/cnych/promoter/template" 12 | "github.com/cnych/promoter/util" 13 | "github.com/go-kit/log" 14 | "github.com/go-kit/log/level" 15 | "github.com/prometheus/common/model" 16 | ) 17 | 18 | type Notifier interface { 19 | Notify(ctx context.Context, alerts *Data) (bool, error) 20 | } 21 | 22 | // Pair is a key/value string pair. 23 | type Pair struct { 24 | Name, Value string 25 | } 26 | 27 | // Pairs is a list of key/value string pairs. 28 | type Pairs []Pair 29 | 30 | // Names returns a list of names of the pairs. 31 | func (ps Pairs) Names() []string { 32 | ns := make([]string, 0, len(ps)) 33 | for _, p := range ps { 34 | ns = append(ns, p.Name) 35 | } 36 | return ns 37 | } 38 | 39 | // Values returns a list of values of the pairs. 40 | func (ps Pairs) Values() []string { 41 | vs := make([]string, 0, len(ps)) 42 | for _, p := range ps { 43 | vs = append(vs, p.Value) 44 | } 45 | return vs 46 | } 47 | 48 | // KV is a set of key/value string pairs. 49 | type KV map[string]string 50 | 51 | // SortedPairs returns a sorted list of key/value pairs. 52 | func (kv KV) SortedPairs() Pairs { 53 | var ( 54 | pairs = make([]Pair, 0, len(kv)) 55 | keys = make([]string, 0, len(kv)) 56 | sortStart = 0 57 | ) 58 | for k := range kv { 59 | if k == string(model.AlertNameLabel) { 60 | keys = append([]string{k}, keys...) 61 | sortStart = 1 62 | } else { 63 | keys = append(keys, k) 64 | } 65 | } 66 | sort.Strings(keys[sortStart:]) 67 | 68 | for _, k := range keys { 69 | pairs = append(pairs, Pair{k, kv[k]}) 70 | } 71 | return pairs 72 | } 73 | 74 | // Remove returns a copy of the key/value set without the given keys. 75 | func (kv KV) Remove(keys []string) KV { 76 | keySet := make(map[string]struct{}, len(keys)) 77 | for _, k := range keys { 78 | keySet[k] = struct{}{} 79 | } 80 | 81 | res := KV{} 82 | for k, v := range kv { 83 | if _, ok := keySet[k]; !ok { 84 | res[k] = v 85 | } 86 | } 87 | return res 88 | } 89 | 90 | // Names returns the names of the label names in the LabelSet. 91 | func (kv KV) Names() []string { 92 | return kv.SortedPairs().Names() 93 | } 94 | 95 | // Values returns a list of the values in the LabelSet. 96 | func (kv KV) Values() []string { 97 | return kv.SortedPairs().Values() 98 | } 99 | 100 | // Data is the data passed to notification templates and webhook pushes. 101 | // 102 | // End-users should not be exposed to Go's type system, as this will confuse them and prevent 103 | // simple things like simple equality checks to fail. Map everything to float64/string. 104 | type Data struct { 105 | Receiver string `json:"receiver"` 106 | Status string `json:"status"` 107 | Alerts Alerts `json:"alerts"` 108 | 109 | GroupLabels KV `json:"groupLabels"` 110 | CommonLabels KV `json:"commonLabels"` 111 | CommonAnnotations KV `json:"commonAnnotations"` 112 | 113 | ExternalURL string `json:"externalURL"` 114 | } 115 | 116 | func (d *Data) MakeAlertImages(logger log.Logger, config *config.Config) error { 117 | for i := range d.Alerts { 118 | generatorUrl, err := url.Parse(d.Alerts[i].GeneratorURL) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | generatorQuery, err := url.ParseQuery(generatorUrl.RawQuery) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | var alertFormula string 129 | for key, param := range generatorQuery { 130 | if key == "g0.expr" { 131 | alertFormula = param[0] 132 | break 133 | } 134 | } 135 | 136 | plotExpression := GetPlotExpr(logger, alertFormula) 137 | queryTime, duration := d.Alerts[i].getPlotTimeRange() 138 | 139 | for _, expr := range plotExpression { 140 | plot, err := Plot( 141 | logger, 142 | expr, 143 | queryTime, 144 | duration, 145 | time.Duration(config.Global.MetricResolution), 146 | config.Global.PrometheusURL.String(), 147 | d.Alerts[i], 148 | ) 149 | if err != nil { 150 | return fmt.Errorf("Plot error: %v\n", err) 151 | } 152 | 153 | publicURL, err := util.UploadFile( 154 | string(config.S3.AccessKey), 155 | string(config.S3.SecretKey), 156 | config.S3.Endpoint, 157 | config.S3.Bucket, 158 | config.S3.Region, 159 | plot) 160 | if err != nil { 161 | return fmt.Errorf("S3 error: %v\n", err) 162 | } 163 | 164 | level.Debug(logger).Log("msg", "alert image uploaded", "url", publicURL) 165 | d.Alerts[i].Images = append(d.Alerts[i].Images, AlertImage{ 166 | Url: publicURL, 167 | Title: expr.String(), 168 | }) 169 | 170 | } 171 | 172 | } 173 | 174 | return nil 175 | } 176 | 177 | // Alert holds one alert for notification templates. 178 | type Alert struct { 179 | Status string `json:"status"` 180 | Labels KV `json:"labels"` 181 | Annotations KV `json:"annotations"` 182 | StartsAt time.Time `json:"startsAt"` 183 | EndsAt time.Time `json:"endsAt"` 184 | GeneratorURL string `json:"generatorURL"` 185 | Fingerprint string `json:"fingerprint"` 186 | Images []AlertImage 187 | } 188 | 189 | func (a Alert) getPlotTimeRange() (time.Time, time.Duration) { 190 | var queryTime time.Time 191 | var duration time.Duration 192 | if a.StartsAt.Second() > a.EndsAt.Second() { 193 | queryTime = a.StartsAt 194 | duration = time.Minute * 20 195 | } else { 196 | queryTime = a.EndsAt 197 | duration = queryTime.Sub(a.StartsAt) 198 | if duration < time.Minute*20 { 199 | duration = time.Minute * 20 200 | } 201 | } 202 | return queryTime, duration 203 | } 204 | 205 | // Alerts is a list of Alert objects. 206 | type Alerts []Alert 207 | 208 | // Firing returns the subset of alerts that are firing. 209 | func (as Alerts) Firing() []Alert { 210 | var res []Alert 211 | for _, a := range as { 212 | if a.Status == string(model.AlertFiring) { 213 | res = append(res, a) 214 | } 215 | } 216 | return res 217 | } 218 | 219 | // Resolved returns the subset of alerts that are resolved. 220 | func (as Alerts) Resolved() []Alert { 221 | var res []Alert 222 | for _, a := range as { 223 | if a.Status == string(model.AlertResolved) { 224 | res = append(res, a) 225 | } 226 | } 227 | return res 228 | } 229 | 230 | // TmplText is using monadic error handling in order to make string templating 231 | // less verbose. Use with care as the final error checking is easily missed. 232 | func TmplText(tmpl *template.Template, data *Data, err *error) func(string) string { 233 | return func(text string) (s string) { 234 | if *err != nil { 235 | return 236 | } 237 | s, *err = tmpl.ExecuteTextString(text, data) 238 | return s 239 | } 240 | } 241 | 242 | // TmplHTML is using monadic error handling in order to make string templating 243 | // less verbose. Use with care as the final error checking is easily missed. 244 | func TmplHTML(tmpl *template.Template, data *Data, err *error) func(string) string { 245 | return func(name string) (s string) { 246 | if *err != nil { 247 | return 248 | } 249 | s, *err = tmpl.ExecuteHTMLString(name, data) 250 | return s 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /notify/plot.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "image/color" 7 | "io" 8 | "regexp" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/go-kit/log" 13 | "github.com/go-kit/log/level" 14 | prometheus "github.com/prometheus/client_golang/api" 15 | prometheusApi "github.com/prometheus/client_golang/api/prometheus/v1" 16 | promModel "github.com/prometheus/common/model" 17 | "github.com/prometheus/prometheus/promql" 18 | "gonum.org/v1/plot" 19 | "gonum.org/v1/plot/palette/brewer" 20 | "gonum.org/v1/plot/plotter" 21 | "gonum.org/v1/plot/vg" 22 | "gonum.org/v1/plot/vg/draw" 23 | ) 24 | 25 | type AlertImage struct { 26 | Url string `json:"url"` 27 | Title string `json:"title"` 28 | } 29 | 30 | type PlotExpr struct { 31 | Formula string 32 | Operator string 33 | Level float64 34 | } 35 | 36 | func (expr PlotExpr) String() string { 37 | return fmt.Sprintf("%s %s %.2f", expr.Formula, expr.Operator, expr.Level) 38 | } 39 | 40 | // Only show important part of metric name 41 | var labelText = regexp.MustCompile("{(.*)}") 42 | 43 | func GetPlotExpr(logger log.Logger, alertFormula string) []PlotExpr { 44 | expr, _ := promql.ParseExpr(alertFormula) 45 | if parenExpr, ok := expr.(*promql.ParenExpr); ok { 46 | expr = parenExpr.Expr 47 | level.Debug(logger).Log("msg", "Removing redundant brackets", "expr", expr.String()) 48 | } 49 | 50 | if binaryExpr, ok := expr.(*promql.BinaryExpr); ok { 51 | var alertOperator string 52 | 53 | switch binaryExpr.Op { 54 | case promql.ItemLAND: 55 | level.Debug(logger).Log("msg", "Logical condition, drawing sides separately") 56 | return append(GetPlotExpr(logger, binaryExpr.LHS.String()), GetPlotExpr(logger, binaryExpr.RHS.String())...) 57 | case promql.ItemLTE, promql.ItemLSS: 58 | alertOperator = "<" 59 | case promql.ItemGTE, promql.ItemGTR: 60 | alertOperator = ">" 61 | default: 62 | level.Debug(logger).Log("msg", "Unexpected operator", "Op", binaryExpr.Op.String()) 63 | alertOperator = ">" 64 | } 65 | 66 | alertLevel, _ := strconv.ParseFloat(binaryExpr.RHS.String(), 64) 67 | return []PlotExpr{{ 68 | Formula: binaryExpr.LHS.String(), 69 | Operator: alertOperator, 70 | Level: alertLevel, 71 | }} 72 | } else { 73 | level.Debug(logger).Log("msg", "Non binary expression", "expr", alertFormula) 74 | return nil 75 | } 76 | } 77 | 78 | func Plot(logger log.Logger, expr PlotExpr, queryTime time.Time, duration, resolution time.Duration, prometheusUrl string, alert Alert) (io.WriterTo, error) { 79 | level.Debug(logger).Log("msg", "Querying Prometheus", "expr", expr.Formula) 80 | metrics, err := Metrics( 81 | prometheusUrl, 82 | expr.Formula, 83 | queryTime, 84 | duration, 85 | resolution, 86 | ) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | var selectedMetrics promModel.Matrix 92 | var founded bool 93 | for _, metric := range metrics { 94 | founded = false 95 | for label, value := range metric.Metric { 96 | if originValue, ok := alert.Labels[string(label)]; ok { 97 | if originValue == string(value) { 98 | founded = true 99 | } else { 100 | founded = false 101 | break 102 | } 103 | } 104 | } 105 | 106 | if founded { 107 | level.Debug(logger).Log("msg", "Best match founded", "metric", metric.Metric) 108 | selectedMetrics = promModel.Matrix{metric} 109 | break 110 | } 111 | } 112 | 113 | if !founded { 114 | level.Debug(logger).Log("msg", "Best match not founded, use entire dataset. Labels to search", "labels", alert.Labels.Values()) 115 | selectedMetrics = metrics 116 | } 117 | 118 | level.Debug(logger).Log("msg", "Creating plot", "summary", alert.Annotations["summary"]) 119 | 120 | plottedMetric, err := PlotMetric(selectedMetrics, expr.Level, expr.Operator) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | return plottedMetric, nil 126 | } 127 | 128 | func PlotMetric(metrics promModel.Matrix, level float64, direction string) (io.WriterTo, error) { 129 | p, err := plot.New() 130 | if err != nil { 131 | return nil, fmt.Errorf("failed to create new plot: %v", err) 132 | } 133 | 134 | textFont, err := vg.MakeFont("Helvetica", 3*vg.Millimeter) 135 | if err != nil { 136 | return nil, fmt.Errorf("failed to load font: %v", err) 137 | } 138 | 139 | evalTextFont, err := vg.MakeFont("Helvetica", 5*vg.Millimeter) 140 | if err != nil { 141 | return nil, fmt.Errorf("failed to load font: %v", err) 142 | } 143 | 144 | evalTextStyle := draw.TextStyle{ 145 | Color: color.NRGBA{A: 150}, 146 | Font: evalTextFont, 147 | XAlign: draw.XRight, 148 | YAlign: draw.YBottom, 149 | } 150 | 151 | p.X.Tick.Marker = plot.TimeTicks{ 152 | Time: plot.UnixTimeIn(time.Local), // use local time 153 | Format: "15:04:05", 154 | } 155 | p.X.Tick.Label.Font = textFont 156 | p.Y.Tick.Label.Font = textFont 157 | p.Legend.Font = textFont 158 | p.Legend.Top = true 159 | p.Legend.YOffs = 15 * vg.Millimeter 160 | 161 | // Color palette for drawing lines 162 | paletteSize := 8 163 | palette, err := brewer.GetPalette(brewer.TypeAny, "Dark2", paletteSize) 164 | if err != nil { 165 | return nil, fmt.Errorf("failed to get color palette: %v", err) 166 | } 167 | colors := palette.Colors() 168 | 169 | var lastEvalValue float64 170 | 171 | for s, sample := range metrics { 172 | data := make(plotter.XYs, 0) 173 | for _, v := range sample.Values { 174 | fs := v.Value.String() 175 | if fs == "NaN" { 176 | _, err := drawLine(data, colors, s, paletteSize, p, metrics, sample) 177 | if err != nil { 178 | return nil, err 179 | } 180 | 181 | data = make(plotter.XYs, 0) 182 | continue 183 | } 184 | 185 | f, err := strconv.ParseFloat(fs, 64) 186 | if err != nil { 187 | return nil, fmt.Errorf("sample value not float: %s", v.Value.String()) 188 | } 189 | data = append(data, plotter.XY{X: float64(v.Timestamp.Unix()), Y: f}) 190 | lastEvalValue = f 191 | } 192 | 193 | _, err := drawLine(data, colors, s, paletteSize, p, metrics, sample) 194 | if err != nil { 195 | return nil, err 196 | } 197 | } 198 | 199 | var polygonPoints plotter.XYs 200 | 201 | if direction == "<" { 202 | polygonPoints = plotter.XYs{{X: p.X.Min, Y: level}, {X: p.X.Max, Y: level}, {X: p.X.Max, Y: p.Y.Min}, {X: p.X.Min, Y: p.Y.Min}} 203 | } else { 204 | polygonPoints = plotter.XYs{{X: p.X.Min, Y: level}, {X: p.X.Max, Y: level}, {X: p.X.Max, Y: p.Y.Max}, {X: p.X.Min, Y: p.Y.Max}} 205 | } 206 | 207 | poly, err := plotter.NewPolygon(polygonPoints) 208 | if err != nil { 209 | return nil, err 210 | } 211 | poly.Color = color.NRGBA{R: 255, A: 40} 212 | poly.LineStyle.Color = color.NRGBA{R: 0, A: 0} 213 | p.Add(poly) 214 | p.Add(plotter.NewGrid()) 215 | 216 | // Draw plot in canvas with margin 217 | margin := 6 * vg.Millimeter 218 | width := 20 * vg.Centimeter 219 | height := 10 * vg.Centimeter 220 | c, err := draw.NewFormattedCanvas(width, height, "png") 221 | if err != nil { 222 | return nil, fmt.Errorf("failed to create canvas: %v", err) 223 | } 224 | 225 | cropedCanvas := draw.Crop(draw.New(c), margin, -margin, margin, -margin) 226 | p.Draw(cropedCanvas) 227 | 228 | // Draw last evaluated value 229 | evalText := fmt.Sprintf("latest evaluation: %.2f", lastEvalValue) 230 | 231 | plotterCanvas := p.DataCanvas(cropedCanvas) 232 | 233 | trX, trY := p.Transforms(&plotterCanvas) 234 | evalRectangle := evalTextStyle.Rectangle(evalText) 235 | 236 | points := []vg.Point{ 237 | {X: trX(p.X.Max) + evalRectangle.Min.X - 8*vg.Millimeter, Y: trY(lastEvalValue) + evalRectangle.Min.Y - vg.Millimeter}, 238 | {X: trX(p.X.Max) + evalRectangle.Min.X - 8*vg.Millimeter, Y: trY(lastEvalValue) + evalRectangle.Max.Y + vg.Millimeter}, 239 | {X: trX(p.X.Max) + evalRectangle.Max.X - 6*vg.Millimeter, Y: trY(lastEvalValue) + evalRectangle.Max.Y + vg.Millimeter}, 240 | {X: trX(p.X.Max) + evalRectangle.Max.X - 6*vg.Millimeter, Y: trY(lastEvalValue) + evalRectangle.Min.Y - vg.Millimeter}, 241 | } 242 | plotterCanvas.FillPolygon(color.NRGBA{R: 255, G: 255, B: 255, A: 90}, points) 243 | plotterCanvas.FillText(evalTextStyle, vg.Point{X: trX(p.X.Max) - 6*vg.Millimeter, Y: trY(lastEvalValue)}, evalText) 244 | 245 | return c, nil 246 | } 247 | 248 | func drawLine(data plotter.XYs, colors []color.Color, s int, paletteSize int, p *plot.Plot, metrics promModel.Matrix, sample *promModel.SampleStream) (*plotter.Line, error) { 249 | var l *plotter.Line 250 | var err error 251 | if len(data) > 0 { 252 | l, err = plotter.NewLine(data) 253 | if err != nil { 254 | return &plotter.Line{}, fmt.Errorf("failed to create line: %v", err) 255 | } 256 | 257 | l.LineStyle.Width = vg.Points(1) 258 | l.LineStyle.Color = colors[s%paletteSize] 259 | 260 | p.Add(l) 261 | if len(metrics) > 1 { 262 | m := labelText.FindStringSubmatch(sample.Metric.String()) 263 | if m != nil { 264 | p.Legend.Add(m[1], l) 265 | } 266 | } 267 | } 268 | 269 | return l, nil 270 | } 271 | 272 | func Metrics(server, query string, queryTime time.Time, duration, step time.Duration) (promModel.Matrix, error) { 273 | client, err := prometheus.NewClient(prometheus.Config{Address: server}) 274 | if err != nil { 275 | return nil, fmt.Errorf("failed to create Prometheus client: %v", err) 276 | } 277 | 278 | api := prometheusApi.NewAPI(client) 279 | value, _, err := api.QueryRange(context.Background(), query, prometheusApi.Range{ 280 | Start: queryTime.Add(-duration), 281 | End: queryTime, 282 | Step: duration / step, 283 | }) 284 | if err != nil { 285 | return nil, fmt.Errorf("failed to query Prometheus: %v", err) 286 | } 287 | 288 | metrics, ok := value.(promModel.Matrix) 289 | if !ok { 290 | return nil, fmt.Errorf("unsupported result format: %s", value.Type().String()) 291 | } 292 | 293 | return metrics, nil 294 | } 295 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | 15 | # A common Makefile that includes rules to be reused in different prometheus projects. 16 | # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! 17 | 18 | # Example usage : 19 | # Create the main Makefile in the root project directory. 20 | # include Makefile.common 21 | # customTarget: 22 | # @echo ">> Running customTarget" 23 | # 24 | 25 | # Ensure GOBIN is not set during build so that promu is installed to the correct path 26 | unexport GOBIN 27 | 28 | GO ?= go 29 | GOFMT ?= $(GO)fmt 30 | FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) 31 | GOOPTS ?= 32 | GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) 33 | GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) 34 | 35 | GO_VERSION ?= $(shell $(GO) version) 36 | GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) 37 | PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') 38 | 39 | GOVENDOR := 40 | GO111MODULE := 41 | ifeq (, $(PRE_GO_111)) 42 | ifneq (,$(wildcard go.mod)) 43 | # Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI). 44 | GO111MODULE := on 45 | 46 | ifneq (,$(wildcard vendor)) 47 | # Always use the local vendor/ directory to satisfy the dependencies. 48 | GOOPTS := $(GOOPTS) -mod=vendor 49 | endif 50 | endif 51 | else 52 | ifneq (,$(wildcard go.mod)) 53 | ifneq (,$(wildcard vendor)) 54 | $(warning This repository requires Go >= 1.11 because of Go modules) 55 | $(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)') 56 | endif 57 | else 58 | # This repository isn't using Go modules (yet). 59 | GOVENDOR := $(FIRST_GOPATH)/bin/govendor 60 | endif 61 | endif 62 | PROMU := $(FIRST_GOPATH)/bin/promu 63 | pkgs = ./... 64 | 65 | ifeq (arm, $(GOHOSTARCH)) 66 | GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) 67 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) 68 | else 69 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) 70 | endif 71 | 72 | GOTEST := $(GO) test 73 | GOTEST_DIR := 74 | ifneq ($(CIRCLE_JOB),) 75 | ifneq ($(shell which gotestsum),) 76 | GOTEST_DIR := test-results 77 | GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- 78 | endif 79 | endif 80 | 81 | PROMU_VERSION ?= 0.13.0 82 | PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz 83 | 84 | GOLANGCI_LINT := 85 | GOLANGCI_LINT_OPTS ?= 86 | GOLANGCI_LINT_VERSION ?= v1.42.0 87 | # golangci-lint only supports linux, darwin and windows platforms on i386/amd64. 88 | # windows isn't included here because of the path separator being different. 89 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) 90 | ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) 91 | # If we're in CI and there is an Actions file, that means the linter 92 | # is being run in Actions, so we don't need to run it here. 93 | ifeq (,$(CIRCLE_JOB)) 94 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 95 | else ifeq (,$(wildcard .github/workflows/golangci-lint.yml)) 96 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 97 | endif 98 | endif 99 | endif 100 | 101 | PREFIX ?= $(shell pwd) 102 | BIN_DIR ?= $(shell pwd) 103 | DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) 104 | DOCKERFILE_PATH ?= ./Dockerfile 105 | DOCKERBUILD_CONTEXT ?= ./ 106 | DOCKER_REPO ?= cnych 107 | 108 | DOCKER_ARCHS ?= amd64 109 | 110 | BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) 111 | PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) 112 | TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) 113 | 114 | ifeq ($(GOHOSTARCH),amd64) 115 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) 116 | # Only supported on amd64 117 | test-flags := -race 118 | endif 119 | endif 120 | 121 | # This rule is used to forward a target like "build" to "common-build". This 122 | # allows a new "build" target to be defined in a Makefile which includes this 123 | # one and override "common-build" without override warnings. 124 | %: common-% ; 125 | 126 | .PHONY: common-all 127 | common-all: precheck style lint yamllint unused build test 128 | 129 | .PHONY: common-style 130 | common-style: 131 | @echo ">> checking code style" 132 | @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ 133 | if [ -n "$${fmtRes}" ]; then \ 134 | echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ 135 | echo "Please ensure you are using $$($(GO) version) for formatting code."; \ 136 | exit 1; \ 137 | fi 138 | 139 | .PHONY: common-check_license 140 | common-check_license: 141 | @echo ">> checking license header" 142 | @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ 143 | awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ 144 | done); \ 145 | if [ -n "$${licRes}" ]; then \ 146 | echo "license header checking failed:"; echo "$${licRes}"; \ 147 | exit 1; \ 148 | fi 149 | 150 | .PHONY: common-deps 151 | common-deps: 152 | @echo ">> getting dependencies" 153 | ifdef GO111MODULE 154 | GO111MODULE=$(GO111MODULE) $(GO) mod download 155 | else 156 | $(GO) get $(GOOPTS) -t ./... 157 | endif 158 | 159 | .PHONY: update-go-deps 160 | update-go-deps: 161 | @echo ">> updating Go dependencies" 162 | @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ 163 | $(GO) get -d $$m; \ 164 | done 165 | GO111MODULE=$(GO111MODULE) $(GO) mod tidy 166 | ifneq (,$(wildcard vendor)) 167 | GO111MODULE=$(GO111MODULE) $(GO) mod vendor 168 | endif 169 | 170 | .PHONY: common-test-short 171 | common-test-short: $(GOTEST_DIR) 172 | @echo ">> running short tests" 173 | GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs) 174 | 175 | .PHONY: common-test 176 | common-test: $(GOTEST_DIR) 177 | @echo ">> running all tests" 178 | GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) 179 | 180 | $(GOTEST_DIR): 181 | @mkdir -p $@ 182 | 183 | .PHONY: common-format 184 | common-format: 185 | @echo ">> formatting code" 186 | GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs) 187 | 188 | .PHONY: common-vet 189 | common-vet: 190 | @echo ">> vetting code" 191 | GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs) 192 | 193 | .PHONY: common-lint 194 | common-lint: $(GOLANGCI_LINT) 195 | ifdef GOLANGCI_LINT 196 | @echo ">> running golangci-lint" 197 | ifdef GO111MODULE 198 | # 'go list' needs to be executed before staticcheck to prepopulate the modules cache. 199 | # Otherwise staticcheck might fail randomly for some reason not yet explained. 200 | GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null 201 | GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) 202 | else 203 | $(GOLANGCI_LINT) run $(pkgs) 204 | endif 205 | endif 206 | 207 | .PHONY: common-yamllint 208 | common-yamllint: 209 | @echo ">> running yamllint on all YAML files in the repository" 210 | ifeq (, $(shell which yamllint)) 211 | @echo "yamllint not installed so skipping" 212 | else 213 | yamllint . 214 | endif 215 | 216 | # For backward-compatibility. 217 | .PHONY: common-staticcheck 218 | common-staticcheck: lint 219 | 220 | .PHONY: common-unused 221 | common-unused: $(GOVENDOR) 222 | ifdef GOVENDOR 223 | @echo ">> running check for unused packages" 224 | @$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages' 225 | else 226 | ifdef GO111MODULE 227 | @echo ">> running check for unused/missing packages in go.mod" 228 | GO111MODULE=$(GO111MODULE) $(GO) mod tidy 229 | ifeq (,$(wildcard vendor)) 230 | @git diff --exit-code -- go.sum go.mod 231 | else 232 | @echo ">> running check for unused packages in vendor/" 233 | GO111MODULE=$(GO111MODULE) $(GO) mod vendor 234 | @git diff --exit-code -- go.sum go.mod vendor/ 235 | endif 236 | endif 237 | endif 238 | 239 | .PHONY: common-build 240 | common-build: promu 241 | @echo ">> building binaries" 242 | GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) 243 | 244 | .PHONY: common-tarball 245 | common-tarball: promu 246 | @echo ">> building release tarball" 247 | $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) 248 | 249 | .PHONY: common-docker $(BUILD_DOCKER_ARCHS) 250 | common-docker: $(BUILD_DOCKER_ARCHS) 251 | $(BUILD_DOCKER_ARCHS): common-docker-%: 252 | docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ 253 | -f $(DOCKERFILE_PATH) \ 254 | --build-arg ARCH="$*" \ 255 | --build-arg OS="linux" \ 256 | $(DOCKERBUILD_CONTEXT) 257 | 258 | .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) 259 | common-docker-publish: $(PUBLISH_DOCKER_ARCHS) 260 | $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: 261 | docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" 262 | 263 | DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) 264 | .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) 265 | common-docker-tag-latest: $(TAG_DOCKER_ARCHS) 266 | $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: 267 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" 268 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" 269 | 270 | .PHONY: common-docker-manifest 271 | common-docker-manifest: 272 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG)) 273 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" 274 | 275 | .PHONY: promu 276 | promu: $(PROMU) 277 | 278 | $(PROMU): 279 | $(eval PROMU_TMP := $(shell mktemp -d)) 280 | curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) 281 | mkdir -p $(FIRST_GOPATH)/bin 282 | cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu 283 | rm -r $(PROMU_TMP) 284 | 285 | ifdef GOLANGCI_LINT 286 | $(GOLANGCI_LINT): 287 | mkdir -p $(FIRST_GOPATH)/bin 288 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ 289 | | sed -e '/install -d/d' \ 290 | | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) 291 | endif 292 | 293 | ifdef GOVENDOR 294 | .PHONY: $(GOVENDOR) 295 | $(GOVENDOR): 296 | GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor 297 | endif 298 | 299 | .PHONY: precheck 300 | precheck:: 301 | 302 | define PRECHECK_COMMAND_template = 303 | precheck:: $(1)_precheck 304 | 305 | PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) 306 | .PHONY: $(1)_precheck 307 | $(1)_precheck: 308 | @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ 309 | echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ 310 | exit 1; \ 311 | fi 312 | endef -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package config 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | "io/ioutil" 20 | "net" 21 | "net/url" 22 | "path/filepath" 23 | "regexp" 24 | "strings" 25 | 26 | "github.com/pkg/errors" 27 | commoncfg "github.com/prometheus/common/config" 28 | "github.com/prometheus/common/model" 29 | "gopkg.in/yaml.v2" 30 | ) 31 | 32 | const secretToken = "" 33 | 34 | var secretTokenJSON string 35 | 36 | func init() { 37 | b, err := json.Marshal(secretToken) 38 | if err != nil { 39 | panic(err) 40 | } 41 | secretTokenJSON = string(b) 42 | } 43 | 44 | // Secret is a string that must not be revealed on marshaling. 45 | type Secret string 46 | 47 | // MarshalYAML implements the yaml.Marshaler interface for Secret. 48 | func (s Secret) MarshalYAML() (interface{}, error) { 49 | if s != "" { 50 | return secretToken, nil 51 | } 52 | return nil, nil 53 | } 54 | 55 | // UnmarshalYAML implements the yaml.Unmarshaler interface for Secret. 56 | func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error { 57 | type plain Secret 58 | return unmarshal((*plain)(s)) 59 | } 60 | 61 | // MarshalJSON implements the json.Marshaler interface for Secret. 62 | func (s Secret) MarshalJSON() ([]byte, error) { 63 | return json.Marshal(secretToken) 64 | } 65 | 66 | // URL is a custom type that represents an HTTP or HTTPS URL and allows validation at configuration load time. 67 | type URL struct { 68 | *url.URL 69 | } 70 | 71 | // Copy makes a deep-copy of the struct. 72 | func (u *URL) Copy() *URL { 73 | v := *u.URL 74 | return &URL{&v} 75 | } 76 | 77 | // MarshalYAML implements the yaml.Marshaler interface for URL. 78 | func (u URL) MarshalYAML() (interface{}, error) { 79 | if u.URL != nil { 80 | return u.URL.String(), nil 81 | } 82 | return nil, nil 83 | } 84 | 85 | // UnmarshalYAML implements the yaml.Unmarshaler interface for URL. 86 | func (u *URL) UnmarshalYAML(unmarshal func(interface{}) error) error { 87 | var s string 88 | if err := unmarshal(&s); err != nil { 89 | return err 90 | } 91 | urlp, err := parseURL(s) 92 | if err != nil { 93 | return err 94 | } 95 | u.URL = urlp.URL 96 | return nil 97 | } 98 | 99 | // MarshalJSON implements the json.Marshaler interface for URL. 100 | func (u URL) MarshalJSON() ([]byte, error) { 101 | if u.URL != nil { 102 | return json.Marshal(u.URL.String()) 103 | } 104 | return []byte("null"), nil 105 | } 106 | 107 | // UnmarshalJSON implements the json.Marshaler interface for URL. 108 | func (u *URL) UnmarshalJSON(data []byte) error { 109 | var s string 110 | if err := json.Unmarshal(data, &s); err != nil { 111 | return err 112 | } 113 | urlp, err := parseURL(s) 114 | if err != nil { 115 | return err 116 | } 117 | u.URL = urlp.URL 118 | return nil 119 | } 120 | 121 | // SecretURL is a URL that must not be revealed on marshaling. 122 | type SecretURL URL 123 | 124 | // MarshalYAML implements the yaml.Marshaler interface for SecretURL. 125 | func (s SecretURL) MarshalYAML() (interface{}, error) { 126 | if s.URL != nil { 127 | return secretToken, nil 128 | } 129 | return nil, nil 130 | } 131 | 132 | // UnmarshalYAML implements the yaml.Unmarshaler interface for SecretURL. 133 | func (s *SecretURL) UnmarshalYAML(unmarshal func(interface{}) error) error { 134 | var str string 135 | if err := unmarshal(&str); err != nil { 136 | return err 137 | } 138 | // In order to deserialize a previously serialized configuration (eg from 139 | // the Alertmanager API with amtool), `` needs to be treated 140 | // specially, as it isn't a valid URL. 141 | if str == secretToken { 142 | s.URL = &url.URL{} 143 | return nil 144 | } 145 | return unmarshal((*URL)(s)) 146 | } 147 | 148 | // MarshalJSON implements the json.Marshaler interface for SecretURL. 149 | func (s SecretURL) MarshalJSON() ([]byte, error) { 150 | return json.Marshal(secretToken) 151 | } 152 | 153 | // UnmarshalJSON implements the json.Marshaler interface for SecretURL. 154 | func (s *SecretURL) UnmarshalJSON(data []byte) error { 155 | // In order to deserialize a previously serialized configuration (eg from 156 | // the Alertmanager API with amtool), `` needs to be treated 157 | // specially, as it isn't a valid URL. 158 | if string(data) == secretToken || string(data) == secretTokenJSON { 159 | s.URL = &url.URL{} 160 | return nil 161 | } 162 | return json.Unmarshal(data, (*URL)(s)) 163 | } 164 | 165 | // Load parses the YAML input s into a Config. 166 | func Load(s string) (*Config, error) { 167 | cfg := &Config{} 168 | err := yaml.UnmarshalStrict([]byte(s), cfg) 169 | if err != nil { 170 | return nil, err 171 | } 172 | 173 | cfg.original = s 174 | return cfg, nil 175 | } 176 | 177 | // LoadFile parses the given YAML file into a Config. 178 | func LoadFile(filename string) (*Config, error) { 179 | content, err := ioutil.ReadFile(filename) 180 | if err != nil { 181 | return nil, err 182 | } 183 | cfg, err := Load(string(content)) 184 | if err != nil { 185 | return nil, err 186 | } 187 | 188 | resolveFilepaths(filepath.Dir(filename), cfg) 189 | return cfg, nil 190 | } 191 | 192 | // resolveFilepaths joins all relative paths in a configuration 193 | // with a given base directory. 194 | func resolveFilepaths(baseDir string, cfg *Config) { 195 | join := func(fp string) string { 196 | if len(fp) > 0 && !filepath.IsAbs(fp) { 197 | fp = filepath.Join(baseDir, fp) 198 | } 199 | return fp 200 | } 201 | 202 | for i, tf := range cfg.Templates { 203 | cfg.Templates[i] = join(tf) 204 | } 205 | 206 | cfg.Global.HTTPConfig.SetDirectory(baseDir) 207 | for _, receiver := range cfg.Receivers { 208 | for _, cfg := range receiver.WechatConfigs { 209 | cfg.HTTPConfig.SetDirectory(baseDir) 210 | } 211 | for _, cfg := range receiver.DingtalkConfigs { 212 | cfg.HTTPConfig.SetDirectory(baseDir) 213 | } 214 | } 215 | } 216 | 217 | // Config 整个应用最顶层的配置文件 218 | type Config struct { 219 | Global *GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"` 220 | Receivers []*Receiver `yaml:"receivers,omitempty" json:"receivers,omitempty"` 221 | Templates []string `yaml:"templates" json:"templates"` 222 | S3 *S3Config `yaml:"s3" json:"s3"` 223 | // original is the input from which the config was parsed. 224 | original string 225 | } 226 | 227 | type S3Config struct { 228 | AccessKey Secret `yaml:"access_key" json:"access_key"` 229 | SecretKey Secret `yaml:"secret_key" json:"secret_key"` 230 | Endpoint string `yaml:"endpoint" json:"endpoint"` 231 | Region string `yaml:"region" json:"region"` 232 | Bucket string `yaml:"bucket" json:"bucket"` 233 | } 234 | 235 | func (c Config) GetReceiver(name string) *Receiver { 236 | for _, rcv := range c.Receivers { 237 | if name == rcv.Name { 238 | return rcv 239 | } 240 | } 241 | return nil 242 | } 243 | 244 | func (c Config) String() string { 245 | b, err := yaml.Marshal(c) 246 | if err != nil { 247 | return fmt.Sprintf("", err) 248 | } 249 | return string(b) 250 | } 251 | 252 | // UnmarshalYAML implements the yaml.Unmarshaler interface for Config. 253 | func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { 254 | // We want to set c to the defaults and then overwrite it with the input. 255 | // To make unmarshal fill the plain data struct rather than calling UnmarshalYAML 256 | // again, we have to hide it using a type indirection. 257 | type plain Config 258 | if err := unmarshal((*plain)(c)); err != nil { 259 | return err 260 | } 261 | 262 | // 如果 global 配置块放开但是没配置内容,则需要用默认的值覆盖 263 | if c.Global == nil { 264 | c.Global = &GlobalConfig{} 265 | *c.Global = DefaultGlobalConfig() 266 | } 267 | 268 | names := map[string]struct{}{} 269 | 270 | for _, rcv := range c.Receivers { 271 | // 接收器配置需要唯一 272 | if _, ok := names[rcv.Name]; ok { 273 | return fmt.Errorf("notification config name %q is not unique", rcv.Name) 274 | } 275 | // 循环 wechat 配置 276 | for _, wcc := range rcv.WechatConfigs { 277 | if wcc.HTTPConfig == nil { 278 | wcc.HTTPConfig = c.Global.HTTPConfig 279 | } 280 | if wcc.APIURL == nil { 281 | if c.Global.WeChatAPIURL == nil { 282 | return fmt.Errorf("no global Wechat URL set") 283 | } 284 | wcc.APIURL = c.Global.WeChatAPIURL 285 | } 286 | if wcc.APISecret == "" { 287 | if c.Global.WeChatAPISecret == "" { 288 | return fmt.Errorf("no global Wechat ApiSecret set") 289 | } 290 | wcc.APISecret = c.Global.WeChatAPISecret 291 | } 292 | if wcc.CorpID == "" { 293 | if c.Global.WeChatAPICorpID == "" { 294 | return fmt.Errorf("no global Wechat CorpID set") 295 | } 296 | wcc.CorpID = c.Global.WeChatAPICorpID 297 | } 298 | if !strings.HasSuffix(wcc.APIURL.Path, "/") { 299 | wcc.APIURL.Path += "/" 300 | } 301 | } 302 | for _, dtc := range rcv.DingtalkConfigs { 303 | if dtc.HTTPConfig == nil { 304 | dtc.HTTPConfig = c.Global.HTTPConfig 305 | } 306 | if dtc.APIURL == nil { 307 | if c.Global.DingTalkAPIURL == nil { 308 | return fmt.Errorf("no global Dingtalk URL set") 309 | } 310 | dtc.APIURL = c.Global.DingTalkAPIURL 311 | } 312 | if dtc.APISecret == "" { 313 | if c.Global.DingTalkAPISecret == "" { 314 | return fmt.Errorf("no global Dingtalk ApiSecret set") 315 | } 316 | dtc.APISecret = c.Global.DingTalkAPISecret 317 | } 318 | if dtc.APIToken == "" { 319 | if c.Global.DingTalkAPIToken == "" { 320 | return fmt.Errorf("no global Dingtalk ApiToken set") 321 | } 322 | dtc.APIToken = c.Global.DingTalkAPIToken 323 | } 324 | } 325 | 326 | names[rcv.Name] = struct{}{} 327 | } 328 | 329 | return nil 330 | } 331 | 332 | // DefaultGlobalConfig 返回带默认值的全局配置 333 | func DefaultGlobalConfig() GlobalConfig { 334 | var defaultHTTPConfig = commoncfg.DefaultHTTPClientConfig 335 | return GlobalConfig{ 336 | MetricResolution: 100, 337 | HTTPConfig: &defaultHTTPConfig, 338 | 339 | WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"), 340 | DingTalkAPIURL: mustParseURL("https://oapi.dingtalk.com/robot/send"), 341 | } 342 | } 343 | 344 | func mustParseURL(s string) *URL { 345 | u, err := parseURL(s) 346 | if err != nil { 347 | panic(err) 348 | } 349 | return u 350 | } 351 | 352 | func parseURL(s string) (*URL, error) { 353 | u, err := url.Parse(s) 354 | if err != nil { 355 | return nil, err 356 | } 357 | if u.Scheme != "http" && u.Scheme != "https" { 358 | return nil, fmt.Errorf("unsupported scheme %q for URL", u.Scheme) 359 | } 360 | if u.Host == "" { 361 | return nil, fmt.Errorf("missing host for URL") 362 | } 363 | return &URL{u}, nil 364 | } 365 | 366 | // HostPort represents a "host:port" network address. 367 | type HostPort struct { 368 | Host string 369 | Port string 370 | } 371 | 372 | // UnmarshalYAML implements the yaml.Unmarshaler interface for HostPort. 373 | func (hp *HostPort) UnmarshalYAML(unmarshal func(interface{}) error) error { 374 | var ( 375 | s string 376 | err error 377 | ) 378 | if err = unmarshal(&s); err != nil { 379 | return err 380 | } 381 | if s == "" { 382 | return nil 383 | } 384 | hp.Host, hp.Port, err = net.SplitHostPort(s) 385 | if err != nil { 386 | return err 387 | } 388 | if hp.Port == "" { 389 | return errors.Errorf("address %q: port cannot be empty", s) 390 | } 391 | return nil 392 | } 393 | 394 | // UnmarshalJSON implements the json.Unmarshaler interface for HostPort. 395 | func (hp *HostPort) UnmarshalJSON(data []byte) error { 396 | var ( 397 | s string 398 | err error 399 | ) 400 | if err = json.Unmarshal(data, &s); err != nil { 401 | return err 402 | } 403 | if s == "" { 404 | return nil 405 | } 406 | hp.Host, hp.Port, err = net.SplitHostPort(s) 407 | if err != nil { 408 | return err 409 | } 410 | if hp.Port == "" { 411 | return errors.Errorf("address %q: port cannot be empty", s) 412 | } 413 | return nil 414 | } 415 | 416 | // MarshalYAML implements the yaml.Marshaler interface for HostPort. 417 | func (hp HostPort) MarshalYAML() (interface{}, error) { 418 | return hp.String(), nil 419 | } 420 | 421 | // MarshalJSON implements the json.Marshaler interface for HostPort. 422 | func (hp HostPort) MarshalJSON() ([]byte, error) { 423 | return json.Marshal(hp.String()) 424 | } 425 | 426 | func (hp HostPort) String() string { 427 | if hp.Host == "" && hp.Port == "" { 428 | return "" 429 | } 430 | return fmt.Sprintf("%s:%s", hp.Host, hp.Port) 431 | } 432 | 433 | // GlobalConfig 定义全局配置参数 434 | type GlobalConfig struct { 435 | ExternalURL *URL `yaml:"external_url,omitempty" json:"external_url,omitempty"` 436 | MetricResolution int64 `yaml:"metric_resolution,omitempty" json:"metric_resolution,omitempty"` 437 | PrometheusURL *URL `yaml:"prometheus_url" json:"prometheus_url"` // 配置 prometheus 地址,方便获取监控图表数据 438 | 439 | HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` 440 | 441 | WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"` 442 | WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"` 443 | WeChatAPICorpID Secret `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"` 444 | 445 | DingTalkAPIURL *URL `yaml:"dingtalk_api_url,omitempty" json:"dingtalk_api_url,omitempty"` 446 | DingTalkAPIToken Secret `yaml:"dingtalk_api_token,omitempty" json:"dingtalk_api_token,omitempty"` 447 | DingTalkAPISecret Secret `yaml:"dingtalk_api_secret,omitempty" json:"dingtalk_api_secret,omitempty"` 448 | } 449 | 450 | // UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig. 451 | func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 452 | *c = DefaultGlobalConfig() 453 | type plain GlobalConfig 454 | return unmarshal((*plain)(c)) 455 | } 456 | 457 | // Receiver configuration provides configuration on how to contact a receiver. 458 | type Receiver struct { 459 | // A unique identifier for this receiver. 460 | Name string `yaml:"name" json:"name"` 461 | 462 | //EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"` 463 | WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"` 464 | DingtalkConfigs []*DingtalkConfig `yaml:"dingtalk_configs,omitempty" json:"dingtalk_configs,omitempty"` 465 | } 466 | 467 | // UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver. 468 | func (c *Receiver) UnmarshalYAML(unmarshal func(interface{}) error) error { 469 | type plain Receiver 470 | if err := unmarshal((*plain)(c)); err != nil { 471 | return err 472 | } 473 | if c.Name == "" { 474 | return fmt.Errorf("missing name in receiver") 475 | } 476 | return nil 477 | } 478 | 479 | // MatchRegexps represents a map of Regexp. 480 | type MatchRegexps map[string]Regexp 481 | 482 | // UnmarshalYAML implements the yaml.Unmarshaler interface for MatchRegexps. 483 | func (m *MatchRegexps) UnmarshalYAML(unmarshal func(interface{}) error) error { 484 | type plain MatchRegexps 485 | if err := unmarshal((*plain)(m)); err != nil { 486 | return err 487 | } 488 | for k, v := range *m { 489 | if !model.LabelNameRE.MatchString(k) { 490 | return fmt.Errorf("invalid label name %q", k) 491 | } 492 | if v.Regexp == nil { 493 | return fmt.Errorf("invalid regexp value for %q", k) 494 | } 495 | } 496 | return nil 497 | } 498 | 499 | // Regexp encapsulates a regexp.Regexp and makes it YAML marshalable. 500 | type Regexp struct { 501 | *regexp.Regexp 502 | original string 503 | } 504 | 505 | // UnmarshalYAML implements the yaml.Unmarshaler interface for Regexp. 506 | func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error { 507 | var s string 508 | if err := unmarshal(&s); err != nil { 509 | return err 510 | } 511 | regex, err := regexp.Compile("^(?:" + s + ")$") 512 | if err != nil { 513 | return err 514 | } 515 | re.Regexp = regex 516 | re.original = s 517 | return nil 518 | } 519 | 520 | // MarshalYAML implements the yaml.Marshaler interface for Regexp. 521 | func (re Regexp) MarshalYAML() (interface{}, error) { 522 | if re.original != "" { 523 | return re.original, nil 524 | } 525 | return nil, nil 526 | } 527 | 528 | // UnmarshalJSON implements the json.Unmarshaler interface for Regexp 529 | func (re *Regexp) UnmarshalJSON(data []byte) error { 530 | var s string 531 | if err := json.Unmarshal(data, &s); err != nil { 532 | return err 533 | } 534 | regex, err := regexp.Compile("^(?:" + s + ")$") 535 | if err != nil { 536 | return err 537 | } 538 | re.Regexp = regex 539 | re.original = s 540 | return nil 541 | } 542 | 543 | // MarshalJSON implements the json.Marshaler interface for Regexp. 544 | func (re Regexp) MarshalJSON() ([]byte, error) { 545 | if re.original != "" { 546 | return json.Marshal(re.original) 547 | } 548 | return []byte("null"), nil 549 | } 550 | -------------------------------------------------------------------------------- /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 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= 34 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 35 | github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 36 | github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 37 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 38 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 39 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 40 | github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= 41 | github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= 42 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 43 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 44 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 45 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 46 | github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 47 | github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= 48 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 49 | github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527 h1:NImof/JkF93OVWZY+PINgl6fPtQyF6f+hNUtZ0QZA1c= 50 | github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 51 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 52 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 53 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 54 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 55 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 56 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= 57 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 58 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 59 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 60 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 61 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 62 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 63 | github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= 64 | github.com/aws/aws-sdk-go v1.43.7 h1:Gbs53KxXJWbO3txoVkevf56bhdDFqRisl7MQQ6581vc= 65 | github.com/aws/aws-sdk-go v1.43.7/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= 66 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 67 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 68 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 69 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 70 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 71 | github.com/biogo/store v0.0.0-20160505134755-913427a1d5e8/go.mod h1:Iev9Q3MErcn+w3UOJD/DkEzllvugfdx7bGcMOFhvr/4= 72 | github.com/cenk/backoff v2.0.0+incompatible/go.mod h1:7FtoeaSnHoZnmZzz47cM35Y9nSW7tNyaidugnHTaFDE= 73 | github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 74 | github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 75 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 76 | github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= 77 | github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 78 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 79 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 80 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 81 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 82 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 83 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 84 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 85 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 86 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 87 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 88 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 89 | github.com/cockroachdb/cmux v0.0.0-20170110192607-30d10be49292/go.mod h1:qRiX68mZX1lGBkTWyp3CLcenw9I94W2dLeRvMzcn9N4= 90 | github.com/cockroachdb/cockroach v0.0.0-20170608034007-84bc9597164f/go.mod h1:xeT/CQ0qZHangbYbWShlCGAx31aV4AjGswDUjhKS6HQ= 91 | github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= 92 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= 93 | github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 94 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 95 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 96 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 97 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 98 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 99 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 100 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 101 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 102 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 103 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 104 | github.com/elastic/gosigar v0.9.0/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= 105 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 106 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 107 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 108 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 109 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 110 | github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 111 | github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= 112 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 113 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 114 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 115 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 116 | github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= 117 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 118 | github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 119 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= 120 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 121 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 122 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 123 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 124 | github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 125 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 126 | github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= 127 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 128 | github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= 129 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 130 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 131 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 132 | github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= 133 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 134 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 135 | github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= 136 | github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 137 | github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 138 | github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 139 | github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 140 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 141 | github.com/go-openapi/jsonpointer v0.17.2/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 142 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 143 | github.com/go-openapi/jsonreference v0.17.2/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 144 | github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 145 | github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 146 | github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= 147 | github.com/go-openapi/runtime v0.18.0/go.mod h1:uI6pHuxWYTy94zZxgcwJkUWa9wbIlhteGfloI10GD4U= 148 | github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 149 | github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 150 | github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 151 | github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 152 | github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= 153 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 154 | github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 155 | github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 156 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 157 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 158 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 159 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 160 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 161 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 162 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 163 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 164 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 165 | github.com/golang/groupcache v0.0.0-20180924190550-6f2cf27854a4/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 166 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 167 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 168 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 169 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 170 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 171 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 172 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 173 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 174 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 175 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 176 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 177 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 178 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 179 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 180 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 181 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 182 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 183 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 184 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 185 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 186 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 187 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 188 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 189 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 190 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 191 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 192 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 193 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 194 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 195 | github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= 196 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 197 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 198 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 199 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 200 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 201 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 202 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 203 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 204 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 205 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 206 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 207 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 208 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 209 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 210 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 211 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 212 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 213 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 214 | github.com/google/pprof v0.0.0-20180605153948-8b03ce837f34/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 215 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 216 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 217 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 218 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 219 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 220 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 221 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 222 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 223 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 224 | github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 225 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 226 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 227 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 228 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 229 | github.com/gophercloud/gophercloud v0.0.0-20190301152420-fca40860790e/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= 230 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 231 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 232 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 233 | github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 234 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= 235 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 236 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 237 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 238 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 239 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 240 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 241 | github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 242 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 243 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 244 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 245 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 246 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 247 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 248 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 249 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 250 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 251 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 252 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 253 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 254 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 255 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 256 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 257 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 258 | github.com/influxdata/influxdb v0.0.0-20170331210902-15e594fc09f1/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= 259 | github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= 260 | github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= 261 | github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 262 | github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 263 | github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 264 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 265 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 266 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 267 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 268 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 269 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 270 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 271 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 272 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 273 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 274 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 275 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 276 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 277 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 278 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 279 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 280 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 281 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0= 282 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 283 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 284 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 285 | github.com/knz/strtime v0.0.0-20181018220328-af2256ee352c/go.mod h1:4ZxfWkxwtc7dBeifERVVWRy9F9rTU9p0yCDgeCtlius= 286 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 287 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 288 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 289 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 290 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 291 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 292 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 293 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 294 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 295 | github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 296 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 297 | github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI= 298 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 299 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 300 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 301 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 302 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 303 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 304 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 305 | github.com/miekg/dns v1.1.10/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 306 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 307 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 308 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 309 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 310 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 311 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 312 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 313 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 314 | github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 315 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 316 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 317 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 318 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 319 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 320 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 321 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 322 | github.com/montanaflynn/stats v0.0.0-20180911141734-db72e6cae808/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 323 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 324 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= 325 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 326 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 327 | github.com/oklog/ulid v0.0.0-20170117200651-66bb6560562f/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 328 | github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= 329 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 330 | github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 331 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 332 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 333 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 334 | github.com/opentracing-contrib/go-stdlib v0.0.0-20170113013457-1de4cc2120e7/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= 335 | github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= 336 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 337 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 338 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 339 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 340 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 341 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 342 | github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea/go.mod h1:1VcHEd3ro4QMoHfiNl/j7Jkln9+KQuorp0PItHMJYNg= 343 | github.com/petermattis/goid v0.0.0-20170504144140-0ded85884ba5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= 344 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 345 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 346 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 347 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 348 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 349 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 350 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 351 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 352 | github.com/prometheus/alertmanager v0.17.0/go.mod h1:3/vUuD9sDlkVuB2KLczjrlG7aqT09pyK0jfTp/itWS0= 353 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 354 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 355 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 356 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 357 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 358 | github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= 359 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 360 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 361 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 362 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 363 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 364 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 365 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 366 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 367 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 368 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 369 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 370 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 371 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= 372 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 373 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 374 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 375 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 376 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 377 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 378 | github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= 379 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 380 | github.com/prometheus/prometheus v0.0.0-20180315085919-58e2a31db8de/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= 381 | github.com/prometheus/prometheus v1.8.2-0.20190814100549-343d8d75fd76 h1:e2o/esUf14u2p5rcx88pYS/fmsDKxlZNad/vwhzCAJc= 382 | github.com/prometheus/prometheus v1.8.2-0.20190814100549-343d8d75fd76/go.mod h1:11Mk7Gzjuke9GloQr0K9Rltwvz4fGeuU7/YlzqcHCPE= 383 | github.com/prometheus/tsdb v0.9.1 h1:IWaAmWkYlgG7/S4iw4IpAQt5Y35QaZM6/GsZ7GsjAuk= 384 | github.com/prometheus/tsdb v0.9.1/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= 385 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 386 | github.com/rlmcpherson/s3gof3r v0.5.0/go.mod h1:s7vv7SMDPInkitQMuZzH615G7yWHdrU2r/Go7Bo71Rs= 387 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 388 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 389 | github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 390 | github.com/rubyist/circuitbreaker v2.2.1+incompatible/go.mod h1:Ycs3JgJADPuzJDwffe12k6BZT8hxVi6lFK+gWYJLN4A= 391 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 392 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 393 | github.com/samuel/go-zookeeper v0.0.0-20161028232340-1d7be4effb13/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= 394 | github.com/sasha-s/go-deadlock v0.0.0-20161201235124-341000892f3d/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= 395 | github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 396 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 397 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 398 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 399 | github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= 400 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= 401 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= 402 | github.com/shurcooL/vfsgen v0.0.0-20180825020608-02ddb050ef6b/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= 403 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 404 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 405 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 406 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 407 | github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 408 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 409 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= 410 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 411 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 412 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 413 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 414 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 415 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 416 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 417 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 418 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 419 | github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= 420 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 421 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 422 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 423 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 424 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 425 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 426 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 427 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 428 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 429 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 430 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 431 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 432 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 433 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 434 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 435 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 436 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 437 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 438 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 439 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 440 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 441 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 442 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 443 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 444 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 445 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 446 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 447 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 448 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 449 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 450 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 451 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= 452 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 453 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 454 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 455 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 456 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= 457 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 458 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 459 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 460 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 461 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 462 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 463 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 464 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 465 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 466 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 467 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 468 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 469 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 470 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 471 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 472 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 473 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 474 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 475 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 476 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 477 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 478 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 479 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 480 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 481 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 482 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 483 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 484 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 485 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 486 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 487 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 488 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 489 | golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 490 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 491 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 492 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 493 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 494 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 495 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 496 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 497 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 498 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 499 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 500 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 501 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 502 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 503 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 504 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 505 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 506 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 507 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 508 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 509 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 510 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 511 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 512 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 513 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 514 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= 515 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 516 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 517 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 518 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 519 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 520 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 521 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 522 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= 523 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 524 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 525 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 526 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 527 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 528 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 529 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 530 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 531 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 532 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 533 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 534 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 535 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 536 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 537 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 538 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 539 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 540 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 541 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 542 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 543 | golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 544 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 545 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 546 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 547 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 548 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 549 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 550 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 551 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 552 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 553 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 554 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 555 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 556 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 557 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 558 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 559 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 560 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 561 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 562 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 563 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 564 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 565 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 566 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 567 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 568 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 569 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 570 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 571 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 572 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 573 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 574 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 575 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 576 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 577 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 578 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 579 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 580 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 581 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 582 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 583 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 584 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 585 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 586 | golang.org/x/text v0.3.1-0.20180805044716-cb6730876b98/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 587 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 588 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 589 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 590 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 591 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 592 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 593 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 594 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 595 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 596 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 597 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 598 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 599 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 600 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 601 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 602 | golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 603 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 604 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 605 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 606 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 607 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 608 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 609 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 610 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 611 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 612 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 613 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 614 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 615 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 616 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 617 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 618 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 619 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 620 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 621 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 622 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 623 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 624 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 625 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 626 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 627 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 628 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 629 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 630 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 631 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 632 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 633 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 634 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 635 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 636 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 637 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 638 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 639 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 640 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 641 | golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= 642 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 643 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 644 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 645 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 646 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 647 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 648 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4 h1:nYxTaCPaVoJbxx+vMVnsFb6kw5+6aJCx52m/lmM/Vog= 649 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 650 | gonum.org/v1/plot v0.7.0 h1:Otpxyvra6Ie07ft50OX5BrCfS/BWEMvhsCUHwPEJmLI= 651 | gonum.org/v1/plot v0.7.0/go.mod h1:2wtU6YrrdQAhAF9+MTd5tOQjrov/zF70b1i99Npjvgo= 652 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 653 | google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 654 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 655 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 656 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 657 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 658 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 659 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 660 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 661 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 662 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 663 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 664 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 665 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 666 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 667 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 668 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 669 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 670 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 671 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 672 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 673 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 674 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 675 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 676 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 677 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 678 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 679 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 680 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 681 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 682 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 683 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 684 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 685 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 686 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 687 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 688 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 689 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 690 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 691 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 692 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 693 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 694 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 695 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 696 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 697 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 698 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 699 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 700 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 701 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 702 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 703 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 704 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 705 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 706 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 707 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 708 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 709 | google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 710 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 711 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 712 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 713 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 714 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 715 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 716 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 717 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 718 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 719 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 720 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 721 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 722 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 723 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 724 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 725 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 726 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 727 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 728 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 729 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 730 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 731 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 732 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 733 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 734 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 735 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 736 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 737 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 738 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 739 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 740 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 741 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 742 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 743 | gopkg.in/fsnotify/fsnotify.v1 v1.3.1/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= 744 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 745 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 746 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 747 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 748 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 749 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 750 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 751 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 752 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 753 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 754 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 755 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 756 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 757 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 758 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 759 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 760 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 761 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 762 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 763 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 764 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 765 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 766 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 767 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 768 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 769 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 770 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 771 | k8s.io/kube-openapi v0.0.0-20180629012420-d83b052f768a/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 772 | k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= 773 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 774 | rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= 775 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 776 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 777 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 778 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 779 | --------------------------------------------------------------------------------