Add support for HTTP `POST` body content (#123)

* Add support for HTTP POST body content

* Add tests for POST body content

* Code structure refactor for review
This commit is contained in:
Ravi 2021-10-03 09:30:53 +00:00 committed by GitHub
parent fe891b5b2b
commit bd0852bc25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 263 additions and 5 deletions

View File

@ -91,6 +91,38 @@ TLS configuration supported by this exporter can be found at [exporter-toolkit/w
make build
```
## Sending body content for HTTP `POST`
If `body` paramater is set in config, it will be sent by the exporter as the body content in the scrape request. The HTTP method will also be set as 'POST' in this case.
```yaml
body:
content: |
My static information: {"time_diff": "1m25s", "anotherVar": "some value"}
```
The body content can also be a [Go Template](https://golang.org/pkg/text/template). All the functions from the [Sprig library](https://masterminds.github.io/sprig/) can be used in the template.
All the query parameters sent by prometheus in the scrape query to the exporter, are available as values while rendering the template.
Example using template functions:
```yaml
body:
content: |
{"time_diff": "{{ duration `95` }}","anotherVar": "{{ randInt 12 30 }}"}
templatize: true
```
Example using template functions with values from the query parameters:
```yaml
body:
content: |
{"time_diff": "{{ duration `95` }}","anotherVar": "{{ .myVal | first }}"}
templatize: true
```
Then `curl "http://exporter:7979/probe?target=http://scrape_target:8080/test/data.json&myVal=something"`, would result in sending the following body as the HTTP POST payload to `http://scrape_target:8080/test/data.json`:
```
{"time_diff": "1m35s","anotherVar": "something"}.
```
## Docker
```console

View File

@ -102,7 +102,7 @@ func probeHandler(w http.ResponseWriter, r *http.Request, logger log.Logger, con
return
}
data, err := exporter.FetchJson(ctx, logger, target, config)
data, err := exporter.FetchJson(ctx, logger, target, config, r.URL.Query())
if err != nil {
http.Error(w, "Failed to fetch JSON response. TARGET: "+target+", ERROR: "+err.Error(), http.StatusServiceUnavailable)
return

View File

@ -15,9 +15,11 @@ package cmd
import (
"encoding/base64"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/go-kit/kit/log"
@ -218,3 +220,155 @@ func TestHTTPHeaders(t *testing.T) {
t.Fatalf("Setting custom headers failed unexpectedly. Got: %s", body)
}
}
// Test is the body template is correctly rendered
func TestBodyPostTemplate(t *testing.T) {
bodyTests := []struct {
Body config.ConfigBody
ShouldSucceed bool
Result string
}{
{
Body: config.ConfigBody{Content: "something static like pi, 3.14"},
ShouldSucceed: true,
},
{
Body: config.ConfigBody{Content: "arbitrary dynamic value pass: {{ randInt 12 30 }}", Templatize: false},
ShouldSucceed: true,
},
{
Body: config.ConfigBody{Content: "arbitrary dynamic value fail: {{ randInt 12 30 }}", Templatize: true},
ShouldSucceed: false,
},
{
Body: config.ConfigBody{Content: "templatized mutated value: {{ upper `hello` }} is now all caps", Templatize: true},
Result: "templatized mutated value: HELLO is now all caps",
ShouldSucceed: true,
},
{
Body: config.ConfigBody{Content: "value should be {{ lower `All Small` | trunc 3 }}", Templatize: true},
Result: "value should be all",
ShouldSucceed: true,
},
}
for _, test := range bodyTests {
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
expected := test.Body.Content
if test.Result != "" {
expected = test.Result
}
if got, _ := io.ReadAll(r.Body); string(got) != expected && test.ShouldSucceed {
t.Errorf("POST request body content mismatch, got: %s, expected: %s", got, expected)
}
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest("POST", "http://example.com/foo"+"?target="+target.URL, strings.NewReader(test.Body.Content))
recorder := httptest.NewRecorder()
c := config.Config{Body: test.Body}
probeHandler(recorder, req, log.NewNopLogger(), c)
resp := recorder.Result()
respBody, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
t.Fatalf("POST body content failed. Got: %s", respBody)
}
target.Close()
}
}
// Test is the query parameters are correctly replaced in the provided body template
func TestBodyPostQuery(t *testing.T) {
bodyTests := []struct {
Body config.ConfigBody
ShouldSucceed bool
Result string
QueryParams map[string]string
}{
{
Body: config.ConfigBody{Content: "pi has {{ .piValue | first }} value", Templatize: true},
ShouldSucceed: true,
Result: "pi has 3.14 value",
QueryParams: map[string]string{"piValue": "3.14"},
},
{
Body: config.ConfigBody{Content: `{ "pi": "{{ .piValue | first }}" }`, Templatize: true},
ShouldSucceed: true,
Result: `{ "pi": "3.14" }`,
QueryParams: map[string]string{"piValue": "3.14"},
},
{
Body: config.ConfigBody{Content: "pi has {{ .anotherQuery | first }} value", Templatize: true},
ShouldSucceed: true,
Result: "pi has very high value",
QueryParams: map[string]string{"piValue": "3.14", "anotherQuery": "very high"},
},
{
Body: config.ConfigBody{Content: "pi has {{ .piValue }} value", Templatize: true},
ShouldSucceed: false,
QueryParams: map[string]string{"piValue": "3.14", "anotherQuery": "dummy value"},
},
{
Body: config.ConfigBody{Content: "pi has {{ .piValue }} value", Templatize: true},
ShouldSucceed: true,
Result: "pi has [3.14] value",
QueryParams: map[string]string{"piValue": "3.14", "anotherQuery": "dummy value"},
},
{
Body: config.ConfigBody{Content: "value of {{ upper `pi` | repeat 3 }} is {{ .anotherQuery | first }}", Templatize: true},
ShouldSucceed: true,
Result: "value of PIPIPI is dummy value",
QueryParams: map[string]string{"piValue": "3.14", "anotherQuery": "dummy value"},
},
{
Body: config.ConfigBody{Content: "pi has {{ .piValue }} value", Templatize: true},
ShouldSucceed: true,
Result: "pi has [] value",
},
{
Body: config.ConfigBody{Content: "pi has {{ .piValue | first }} value", Templatize: true},
ShouldSucceed: true,
Result: "pi has <no value> value",
},
{
Body: config.ConfigBody{Content: "value of pi is 3.14", Templatize: true},
ShouldSucceed: true,
},
}
for _, test := range bodyTests {
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
expected := test.Body.Content
if test.Result != "" {
expected = test.Result
}
if got, _ := io.ReadAll(r.Body); string(got) != expected && test.ShouldSucceed {
t.Errorf("POST request body content mismatch (with query params), got: %s, expected: %s", got, expected)
}
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest("POST", "http://example.com/foo"+"?target="+target.URL, strings.NewReader(test.Body.Content))
q := req.URL.Query()
for k, v := range test.QueryParams {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
recorder := httptest.NewRecorder()
c := config.Config{Body: test.Body}
probeHandler(recorder, req, log.NewNopLogger(), c)
resp := recorder.Result()
respBody, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
t.Fatalf("POST body content failed. Got: %s", respBody)
}
target.Close()
}
}

View File

@ -42,6 +42,12 @@ type Config struct {
Headers map[string]string `yaml:"headers,omitempty"`
Metrics []Metric `yaml:"metrics"`
HTTPClientConfig pconfig.HTTPClientConfig `yaml:"http_client_config,omitempty"`
Body ConfigBody `yaml:"body,omitempty"`
}
type ConfigBody struct {
Content string `yaml:"content"`
Templatize bool `yaml:"templatize,omitempty"`
}
func LoadConfig(configPath string) (Config, error) {

View File

@ -22,6 +22,17 @@ metrics:
headers:
X-Dummy: my-test-header
# If 'body' is set, it will be sent by the exporter as the body content in the scrape request. The HTTP method will also be set as 'POST' in this case.
# body:
# content: |
# {"time_diff": "1m25s", "anotherVar": "some value"}
# The body content can also be a Go Template (https://golang.org/pkg/text/template), with all the functions from the Sprig library (https://masterminds.github.io/sprig/) available. All the query parameters sent by prometheus in the scrape query to the exporter, are available in the template.
# body:
# content: |
# {"time_diff": "{{ duration `95` }}","anotherVar": "{{ .myVal | first }}"}
# templatize: true
# For full http client config parameters, ref: https://pkg.go.dev/github.com/prometheus/common/config?tab=doc#HTTPClientConfig
#
# http_client_config:

View File

@ -21,9 +21,12 @@ import (
"io/ioutil"
"math"
"net/http"
"net/url"
"strconv"
"strings"
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/prometheus-community/json_exporter/config"
@ -110,21 +113,27 @@ func CreateMetricsList(c config.Config) ([]JsonMetric, error) {
return metrics, nil
}
func FetchJson(ctx context.Context, logger log.Logger, endpoint string, config config.Config) ([]byte, error) {
httpClientConfig := config.HTTPClientConfig
func FetchJson(ctx context.Context, logger log.Logger, endpoint string, c config.Config, tplValues url.Values) ([]byte, error) {
httpClientConfig := c.HTTPClientConfig
client, err := pconfig.NewClientFromConfig(httpClientConfig, "fetch_json", pconfig.WithKeepAlivesDisabled(), pconfig.WithHTTP2Disabled())
if err != nil {
level.Error(logger).Log("msg", "Error generating HTTP client", "err", err) //nolint:errcheck
return nil, err
}
req, err := http.NewRequest("GET", endpoint, nil)
var req *http.Request
if c.Body.Content == "" {
req, err = http.NewRequest("GET", endpoint, nil)
} else {
req, err = http.NewRequest("POST", endpoint, renderBody(logger, c.Body, tplValues))
}
req = req.WithContext(ctx)
if err != nil {
level.Error(logger).Log("msg", "Failed to create request", "err", err) //nolint:errcheck
return nil, err
}
for key, value := range config.Headers {
for key, value := range c.Headers {
req.Header.Add(key, value)
}
if req.Header.Get("Accept") == "" {
@ -153,3 +162,28 @@ func FetchJson(ctx context.Context, logger log.Logger, endpoint string, config c
return data, nil
}
// Use the configured template to render the body if enabled
// Do not treat template errors as fatal, on such errors just log them
// and continue with static body content
func renderBody(logger log.Logger, body config.ConfigBody, tplValues url.Values) io.Reader {
br := strings.NewReader(body.Content)
if body.Templatize {
tpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).Parse(body.Content)
if err != nil {
level.Error(logger).Log("msg", "Failed to create a new template from body content", "err", err, "content", body.Content) //nolint:errcheck
return br
}
tpl = tpl.Option("missingkey=zero")
var b strings.Builder
if err := tpl.Execute(&b, tplValues); err != nil {
level.Error(logger).Log("msg", "Failed to render template with values", "err", err, "tempalte", body.Content) //nolint:errcheck
// `tplValues` can contain sensitive values, so log it only when in debug mode
level.Debug(logger).Log("msg", "Failed to render template with values", "err", err, "tempalte", body.Content, "values", tplValues, "rendered_body", b.String()) //nolint:errcheck
return br
}
br = strings.NewReader(b.String())
}
return br
}

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/prometheus-community/json_exporter
go 1.14
require (
github.com/Masterminds/sprig/v3 v3.2.2
github.com/go-kit/kit v0.11.0
github.com/prometheus/client_golang v1.11.0
github.com/prometheus/common v0.30.0

20
go.sum
View File

@ -41,6 +41,12 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
@ -194,6 +200,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@ -227,9 +234,13 @@ github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
@ -271,10 +282,14 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -348,6 +363,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
@ -356,6 +373,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
@ -397,6 +416,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=