diff --git a/cmd/generic-handlers.go b/cmd/generic-handlers.go index 0ae75f998..d45f1d6ae 100644 --- a/cmd/generic-handlers.go +++ b/cmd/generic-handlers.go @@ -27,6 +27,7 @@ import ( "github.com/minio/minio/cmd/config/etcd/dns" "github.com/minio/minio/cmd/crypto" xhttp "github.com/minio/minio/cmd/http" + "github.com/minio/minio/cmd/http/stats" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/handlers" "github.com/rs/cors" @@ -535,12 +536,21 @@ func setHTTPStatsHandler(h http.Handler) http.Handler { } func (h httpStatsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - isS3Request := !strings.HasPrefix(r.URL.Path, minioReservedBucketPath) - // record s3 connection stats. - r.Body = &recordTrafficRequest{ReadCloser: r.Body, isS3Request: isS3Request} - recordResponse := &recordTrafficResponse{ResponseWriter: w, isS3Request: isS3Request} + // Meters s3 connection stats. + meteredRequest := &stats.IncomingTrafficMeter{ReadCloser: r.Body} + meteredResponse := &stats.OutgoingTrafficMeter{ResponseWriter: w} + // Execute the request - h.handler.ServeHTTP(recordResponse, r) + r.Body = meteredRequest + h.handler.ServeHTTP(meteredResponse, r) + + if strings.HasPrefix(r.URL.Path, minioReservedBucketPath) { + globalConnStats.incInputBytes(meteredRequest.BytesCount()) + globalConnStats.incOutputBytes(meteredResponse.BytesCount()) + } else { + globalConnStats.incS3InputBytes(meteredRequest.BytesCount()) + globalConnStats.incS3OutputBytes(meteredResponse.BytesCount()) + } } // requestValidityHandler validates all the incoming paths for diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index f2a4d7cd9..2e4332b20 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -28,6 +28,7 @@ import ( "net/url" "regexp" "strings" + "time" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" @@ -35,6 +36,8 @@ import ( "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/handlers" "github.com/minio/minio/pkg/madmin" + + stats "github.com/minio/minio/cmd/http/stats" ) const ( @@ -360,36 +363,19 @@ func httpTraceHdrs(f http.HandlerFunc) http.HandlerFunc { func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + globalHTTPStats.currentS3Requests.Inc(api) + defer globalHTTPStats.currentS3Requests.Dec(api) - isS3Request := !strings.HasPrefix(r.URL.Path, minioReservedBucketPath) - - // Time start before the call is about to start. - tBefore := UTCNow() - - apiStatsWriter := &recordAPIStats{ResponseWriter: w, TTFB: tBefore, isS3Request: isS3Request} - - if isS3Request { - globalHTTPStats.currentS3Requests.Inc(api) - } - - // Execute the request - f.ServeHTTP(apiStatsWriter, r) - - if isS3Request { - globalHTTPStats.currentS3Requests.Dec(api) - } + statsWriter := stats.NewRecordAPIStats(w) - // Firstbyte read. - tAfter := apiStatsWriter.TTFB + f.ServeHTTP(statsWriter, r) // Time duration in secs since the call started. - // // We don't need to do nanosecond precision in this // simply for the fact that it is not human readable. - durationSecs := tAfter.Sub(tBefore).Seconds() + durationSecs := time.Since(statsWriter.StartTime).Seconds() - // Update http statistics - globalHTTPStats.updateStats(api, r, apiStatsWriter, durationSecs) + globalHTTPStats.updateStats(api, r, statsWriter, durationSecs) } } diff --git a/cmd/http-stats.go b/cmd/http-stats.go index de6dcf80b..20e78c969 100644 --- a/cmd/http-stats.go +++ b/cmd/http-stats.go @@ -23,6 +23,7 @@ import ( "sync" "time" + stats "github.com/minio/minio/cmd/http/stats" "github.com/prometheus/client_golang/prometheus" "go.uber.org/atomic" ) @@ -166,18 +167,18 @@ func (st *HTTPStats) toServerHTTPStats() ServerHTTPStats { } // Update statistics from http request and response data -func (st *HTTPStats) updateStats(api string, r *http.Request, w *recordAPIStats, durationSecs float64) { +func (st *HTTPStats) updateStats(api string, r *http.Request, w *stats.RecordAPIStats, durationSecs float64) { // A successful request has a 2xx response code - successReq := (w.respStatusCode >= 200 && w.respStatusCode < 300) + successReq := (w.RespStatusCode >= 200 && w.RespStatusCode < 300) - if w.isS3Request && !strings.HasSuffix(r.URL.Path, prometheusMetricsPath) { + if !strings.HasSuffix(r.URL.Path, prometheusMetricsPath) { st.totalS3Requests.Inc(api) - if !successReq && w.respStatusCode != 0 { + if !successReq && w.RespStatusCode != 0 { st.totalS3Errors.Inc(api) } } - if w.isS3Request && r.Method == "GET" { + if r.Method == "GET" { // Increment the prometheus http request response histogram with appropriate label httpRequestsDuration.With(prometheus.Labels{"api": api}).Observe(durationSecs) } diff --git a/cmd/http-traffic-recorder.go b/cmd/http-traffic-recorder.go deleted file mode 100644 index e2c70bb66..000000000 --- a/cmd/http-traffic-recorder.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - * MinIO Cloud Storage, (C) 2019 MinIO, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "io" - "net/http" - "time" -) - -// records the incoming bytes from the underlying request.Body. -type recordTrafficRequest struct { - io.ReadCloser - isS3Request bool -} - -// Records the bytes read. -func (r *recordTrafficRequest) Read(p []byte) (n int, err error) { - n, err = r.ReadCloser.Read(p) - globalConnStats.incInputBytes(n) - if r.isS3Request { - globalConnStats.incS3InputBytes(n) - } - return n, err -} - -// Records the outgoing bytes through the responseWriter. -type recordTrafficResponse struct { - // wrapper for underlying http.ResponseWriter. - http.ResponseWriter - isS3Request bool -} - -// Records the output bytes -func (r *recordTrafficResponse) Write(p []byte) (n int, err error) { - n, err = r.ResponseWriter.Write(p) - globalConnStats.incOutputBytes(n) - // Check if it is s3 request - if r.isS3Request { - globalConnStats.incS3OutputBytes(n) - } - return n, err -} - -// Calls the underlying Flush. -func (r *recordTrafficResponse) Flush() { - r.ResponseWriter.(http.Flusher).Flush() -} - -// Records the outgoing bytes through the responseWriter. -type recordAPIStats struct { - http.ResponseWriter - TTFB time.Time // TimeToFirstByte. - firstByteRead bool - respStatusCode int - isS3Request bool -} - -// Calls the underlying WriteHeader. -func (r *recordAPIStats) WriteHeader(i int) { - r.respStatusCode = i - r.ResponseWriter.WriteHeader(i) -} - -// Records the TTFB on the first byte write. -func (r *recordAPIStats) Write(p []byte) (n int, err error) { - if !r.firstByteRead { - r.TTFB = UTCNow() - r.firstByteRead = true - } - return r.ResponseWriter.Write(p) -} - -// Calls the underlying Flush. -func (r *recordAPIStats) Flush() { - r.ResponseWriter.(http.Flusher).Flush() -} diff --git a/cmd/http/stats/http-traffic-recorder.go b/cmd/http/stats/http-traffic-recorder.go new file mode 100644 index 000000000..8cb70608c --- /dev/null +++ b/cmd/http/stats/http-traffic-recorder.go @@ -0,0 +1,107 @@ +/* + * MinIO Cloud Storage, (C) 2019-2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package stats + +import ( + "io" + "net/http" + "time" +) + +// IncomingTrafficMeter counts the incoming bytes from the underlying request.Body. +type IncomingTrafficMeter struct { + io.ReadCloser + countBytes int +} + +// Read calls the underlying Read and counts the transferred bytes. +func (r *IncomingTrafficMeter) Read(p []byte) (n int, err error) { + n, err = r.ReadCloser.Read(p) + r.countBytes += n + return n, err +} + +// BytesCount returns the number of transferred bytes +func (r IncomingTrafficMeter) BytesCount() int { + return r.countBytes +} + +// OutgoingTrafficMeter counts the outgoing bytes through the responseWriter. +type OutgoingTrafficMeter struct { + // wrapper for underlying http.ResponseWriter. + http.ResponseWriter + countBytes int +} + +// Write calls the underlying write and counts the output bytes +func (w *OutgoingTrafficMeter) Write(p []byte) (n int, err error) { + n, err = w.ResponseWriter.Write(p) + w.countBytes += n + return n, err +} + +// Flush calls the underlying Flush. +func (w *OutgoingTrafficMeter) Flush() { + w.ResponseWriter.(http.Flusher).Flush() +} + +// BytesCount returns the number of transferred bytes +func (w OutgoingTrafficMeter) BytesCount() int { + return w.countBytes +} + +// RecordAPIStats is a response writer which stores +// information of the underlying http response. +type RecordAPIStats struct { + http.ResponseWriter + TTFB time.Duration // TimeToFirstByte. + StartTime time.Time + RespStatusCode int + + firstByteRead bool +} + +// NewRecordAPIStats creates a new response writer with +// start time set to the function call time. +func NewRecordAPIStats(w http.ResponseWriter) *RecordAPIStats { + return &RecordAPIStats{ + ResponseWriter: w, + StartTime: time.Now().UTC(), + } +} + +// WriteHeader calls the underlying WriteHeader +// and records the response status code. +func (r *RecordAPIStats) WriteHeader(i int) { + r.RespStatusCode = i + r.ResponseWriter.WriteHeader(i) +} + +// Write calls the underlying Write and updates TTFB and other info +func (r *RecordAPIStats) Write(p []byte) (n int, err error) { + if !r.firstByteRead { + r.TTFB = time.Now().UTC().Sub(r.StartTime) + r.firstByteRead = true + } + n, err = r.ResponseWriter.Write(p) + return +} + +// Flush calls the underlying Flush. +func (r *RecordAPIStats) Flush() { + r.ResponseWriter.(http.Flusher).Flush() +} diff --git a/cmd/logger/audit.go b/cmd/logger/audit.go index 1a0638aa2..6e309288d 100644 --- a/cmd/logger/audit.go +++ b/cmd/logger/audit.go @@ -26,6 +26,8 @@ import ( "github.com/gorilla/mux" "github.com/minio/minio/cmd/logger/message/audit" + + stats "github.com/minio/minio/cmd/http/stats" ) // ResponseWriter - is a wrapper to trap the http response status code. @@ -132,14 +134,22 @@ func AddAuditTarget(t Target) { // AuditLog - logs audit logs to all audit targets. func AuditLog(w http.ResponseWriter, r *http.Request, api string, reqClaims map[string]interface{}) { - var statusCode int - var timeToResponse time.Duration - var timeToFirstByte time.Duration - lrw, ok := w.(*ResponseWriter) + // Fast exit if there is not audit target configured + if len(AuditTargets) == 0 { + return + } + + var ( + statusCode int + timeToResponse time.Duration + timeToFirstByte time.Duration + ) + + st, ok := w.(*stats.RecordAPIStats) if ok { - statusCode = lrw.StatusCode - timeToResponse = time.Now().UTC().Sub(lrw.StartTime) - timeToFirstByte = lrw.TimeToFirstByte + statusCode = st.RespStatusCode + timeToResponse = time.Now().UTC().Sub(st.StartTime) + timeToFirstByte = st.TTFB } vars := mux.Vars(r) @@ -149,16 +159,17 @@ func AuditLog(w http.ResponseWriter, r *http.Request, api string, reqClaims map[ object = vars["object"] } + entry := audit.ToEntry(w, r, reqClaims, globalDeploymentID) + entry.API.Name = api + entry.API.Bucket = bucket + entry.API.Object = object + entry.API.Status = http.StatusText(statusCode) + entry.API.StatusCode = statusCode + entry.API.TimeToFirstByte = timeToFirstByte.String() + entry.API.TimeToResponse = timeToResponse.String() + // Send audit logs only to http targets. for _, t := range AuditTargets { - entry := audit.ToEntry(w, r, reqClaims, globalDeploymentID) - entry.API.Name = api - entry.API.Bucket = bucket - entry.API.Object = object - entry.API.Status = http.StatusText(statusCode) - entry.API.StatusCode = statusCode - entry.API.TimeToFirstByte = timeToFirstByte.String() - entry.API.TimeToResponse = timeToResponse.String() _ = t.Send(entry, string(All)) } } diff --git a/go.mod b/go.mod index ce7771def..fc0567a74 100644 --- a/go.mod +++ b/go.mod @@ -114,10 +114,12 @@ require ( go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.10.0 // indirect golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd golang.org/x/text v0.3.2 // indirect + golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32 // indirect google.golang.org/api v0.5.0 google.golang.org/appengine v1.6.0 // indirect google.golang.org/genproto v0.0.0-20190513181449-d00d292a067c // indirect @@ -126,4 +128,5 @@ require ( gopkg.in/ldap.v3 v3.0.3 gopkg.in/olivere/elastic.v5 v5.0.80 gopkg.in/yaml.v2 v2.2.4 + honnef.co/go/tools v0.0.1-2020.1.3 // indirect ) diff --git a/go.sum b/go.sum index 6a4366554..6ed738303 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,7 @@ github.com/cheggaaa/pb v1.0.28 h1:kWGpdAcSp3MxMU9CCHOwz/8V0kCHN4+9yQm2MzWuI98= github.com/cheggaaa/pb v1.0.28/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coredns/coredns v1.4.0 h1:RubBkYmkByUqZWWkjRHvNLnUHgkRVqAWgSMmRFvpE1A= github.com/coredns/coredns v1.4.0/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= @@ -138,6 +139,7 @@ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPg github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57 h1:eqyIo2HjKhKe/mJzTG8n4VqvLXIOEG+SLdDqX7xGtkY= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc= @@ -378,6 +380,7 @@ github.com/rcrowley/go-metrics v0.0.0-20190704165056-9c2d0518ed81/go.mod h1:bCqn github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -436,6 +439,7 @@ github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= @@ -452,8 +456,10 @@ golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4= golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -462,6 +468,11 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -474,6 +485,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -487,6 +499,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -523,7 +536,15 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190914235951-31e00f45c22e h1:nOOVVcLC+/3MeovP40q5lCiWmP1Z1DaN8yn8ngU63hw= golang.org/x/tools v0.0.0-20190914235951-31e00f45c22e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32 h1:Xvf3ZQTm5bjXPxhI7g+dwqsCqadK1rcNtwtszuatetk= +golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.5.0 h1:lj9SyhMzyoa38fgFF0oO2T6pjs5IzkLPKfVtxpyCRMM= @@ -554,6 +575,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.48.0 h1:URjZc+8ugRY5mL5uUeQH/a63JcHwdX9xZaWvmNWD7z8= @@ -584,5 +606,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=