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:
parent
fe891b5b2b
commit
bd0852bc25
32
README.md
32
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
154
cmd/main_test.go
154
cmd/main_test.go
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
1
go.mod
|
@ -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
20
go.sum
|
@ -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=
|
||||
|
|
Loading…
Reference in New Issue