From fda281721a5ffac4cdebb115083a77b2b053ddbb Mon Sep 17 00:00:00 2001 From: scottlepp Date: Fri, 27 Oct 2023 08:42:13 -0400 Subject: [PATCH 1/6] error source --- datasource.go | 8 ++-- datasource_test.go | 54 +++++++++++++++++++++++ errors.go | 11 +++++ go.mod | 43 ++++++++++--------- go.sum | 105 ++++++++++++++++++++++++++------------------- 5 files changed, 152 insertions(+), 69 deletions(-) diff --git a/datasource.go b/datasource.go index f689c7f..9ac64c3 100644 --- a/datasource.go +++ b/datasource.go @@ -135,9 +135,11 @@ func (ds *SQLDatasource) QueryData(ctx context.Context, req *backend.QueryDataRe } } } + response.Set(query.RefID, backend.DataResponse{ - Frames: frames, - Error: err, + Frames: frames, + Error: err, + ErrorSource: ErrorSource(err), }) wg.Done() @@ -241,8 +243,6 @@ func (ds *SQLDatasource) handleQuery(ctx context.Context, req backend.DataQuery, return res, nil } - // err = Unwrap(err) - if errors.Is(err, ErrorNoResults) { return res, nil } diff --git a/datasource_test.go b/datasource_test.go index c51cf30..ad36da2 100644 --- a/datasource_test.go +++ b/datasource_test.go @@ -224,6 +224,60 @@ func Test_error_retries(t *testing.T) { } +func Test_error_source(t *testing.T) { + testCounter = 0 + dsUID := "error" + settings := backend.DataSourceInstanceSettings{UID: dsUID} + + handler := &testSqlHandler{ + error: errors.New("foo"), + } + mockDriver := "sqlmock-error-source" + mock.RegisterDriver(mockDriver, handler) + + errorDriver := fakeDriver{ + openDBfn: func(msg json.RawMessage) (*sql.DB, error) { + db, err := sql.Open(mockDriver, "") + if err != nil { + t.Errorf("failed to connect to mock driver: %v", err) + } + return db, errors.New("foo") + }, + } + retries := 0 + max := time.Duration(10) * time.Second + driverSettings := DriverSettings{Retries: retries, Timeout: max, Pause: 1, RetryOn: []string{"foo"}} + ds := &SQLDatasource{c: errorDriver, driverSettings: driverSettings} + + key := defaultKey(dsUID) + // Add the mandatory default db + db, _ := errorDriver.Connect(context.Background(), settings, nil) + ds.storeDBConnection(key, dbConnection{db, settings}) + ctx := context.Background() + + qry := `{ "rawSql": "foo" }` + + req := &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{ + DataSourceInstanceSettings: &settings, + }, + Queries: []backend.DataQuery{ + { + RefID: "foo", + JSON: []byte(qry), + }, + }, + } + + data, err := ds.QueryData(ctx, req) + assert.Nil(t, err) + assert.NotNil(t, data.Responses) + + res := data.Responses["foo"] + assert.NotNil(t, res.Error) + assert.Equal(t, backend.ErrorSourceDownstream, res.ErrorSource) +} + func Test_query_apply_headers(t *testing.T) { testCounter = 0 dsUID := "headers" diff --git a/errors.go b/errors.go index af2188f..04a9e54 100644 --- a/errors.go +++ b/errors.go @@ -3,6 +3,7 @@ package sqlds import ( "errors" + "github.com/grafana/grafana-plugin-sdk-go/backend" es "github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource" ) @@ -26,3 +27,13 @@ func PluginError(err error, override ...bool) error { func DownstreamError(err error, override ...bool) error { return es.DownstreamError(err, len(override) > 0) } + +type Error es.Error + +func ErrorSource(err error) backend.ErrorSource { + var se es.Error + if errors.As(err, &se) { + return se.Source() + } + return backend.ErrorSourcePlugin +} diff --git a/go.mod b/go.mod index 7f0be01..17aad6f 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,20 @@ module github.com/grafana/sqlds/v3 -go 1.20 +go 1.21 + +toolchain go1.21.1 require ( github.com/go-sql-driver/mysql v1.4.0 github.com/google/go-cmp v0.5.9 - github.com/grafana/grafana-plugin-sdk-go v0.184.0 + github.com/grafana/grafana-plugin-sdk-go v0.188.3 github.com/mithrandie/csvq-driver v1.6.8 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.8.4 ) require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/apache/arrow/go/v13 v13.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect @@ -38,18 +40,17 @@ require ( github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 // indirect - github.com/urfave/cli v1.22.12 // indirect + github.com/urfave/cli v1.22.14 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect - go.opentelemetry.io/otel v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 // indirect - go.opentelemetry.io/otel/metric v1.16.0 // indirect - go.opentelemetry.io/otel/sdk v1.16.0 // indirect - go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.45.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.20.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/sdk v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/tools v0.6.0 // indirect @@ -94,15 +95,15 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - golang.org/x/crypto v0.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230307190834-24139beb5833 - golang.org/x/net v0.12.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/grpc v1.57.0 // indirect + google.golang.org/grpc v1.58.2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e5b1801..4dab01b 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,12 @@ cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/apache/arrow/go/v13 v13.0.0 h1:kELrvDQuKZo8csdWYqBQfyi431x6Zs/YJTEgUuSVcWk= github.com/apache/arrow/go/v13 v13.0.0/go.mod h1:W69eByFNO0ZR30q1/7Sr9d83zcVZmF2MiP3fFYAWJOc= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -24,6 +25,7 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -35,14 +37,18 @@ github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMx github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q= +github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= @@ -63,11 +69,13 @@ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -90,10 +98,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/grafana/grafana-plugin-sdk-go v0.182.0 h1:UDhzFEXDeskgJi5uG/uHJc350vrQp1hi/Eg8KUFw5Io= -github.com/grafana/grafana-plugin-sdk-go v0.182.0/go.mod h1:fPX9spPWEzyUg0BLTQbdXCCq7PVSJZGNVKfNpiTQUts= -github.com/grafana/grafana-plugin-sdk-go v0.184.0 h1:1bIQZnOLqktYwaFybkghE9TmWv+gLKB4plB2wL4DCdQ= -github.com/grafana/grafana-plugin-sdk-go v0.184.0/go.mod h1:fPX9spPWEzyUg0BLTQbdXCCq7PVSJZGNVKfNpiTQUts= +github.com/grafana/grafana-plugin-sdk-go v0.188.3 h1:91wrmnS6zXs4FhriVesZujkmjrwY1xhsusL30CtLkEE= +github.com/grafana/grafana-plugin-sdk-go v0.188.3/go.mod h1:nctofuR6fyhx3Stbnh5ha6setroeqqBgO0Rj9s4t86o= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -109,6 +115,7 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -125,6 +132,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -153,6 +161,7 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mithrandie/csvq v1.17.10 h1:ba8W6rWgB6LfIhY1ttmgXzKNcCoVtT4e6zuZTaGuAQg= github.com/mithrandie/csvq v1.17.10/go.mod h1:ALXIPvYIbBEJvcoB41WSQhhLqOXT+2P4VommU+2DLLc= github.com/mithrandie/csvq-driver v1.6.8 h1:0rF4yZ0ByIECznd9Ld+Ry5tIEYq/zxbb3QYuni/JWFk= @@ -193,6 +202,7 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -214,9 +224,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= @@ -224,45 +235,47 @@ github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnl github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= -github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 h1:ZOLJc06r4CB42laIXg/7udr0pbZyuAihN10A/XuiQRY= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0/go.mod h1:5z+/ZWJQKXa9YT34fQNx5K8Hd1EoIhvtUygUQPqEOgQ= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0 h1:0vzgiFDsCh/jxRCR1xcRrtMoeCu2itXz/PsXst5P8rI= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0/go.mod h1:y0vOY2OKFMOTvwxKfurStPayUUKGHlNeVqNneHmFXr0= -go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWEl3a6Xvd4tw/3xxGO1i05Y= -go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 h1:TVQp/bboR4mhZSav+MdgXB8FaRho1RC8UwVn3T0vjVc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0/go.mod h1:I33vtIe0sR96wfrUcilIzLoA3mLHhRmz9S9Te0S3gDo= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= -go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= -go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.45.0 h1:2ea0IkZBsWH+HA2GkD+7+hRw2u97jzdFyRtXuO14a1s= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.45.0/go.mod h1:4m3RnBBb+7dB9d21y510oO1pdB1V4J6smNf14WXcBFQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/contrib/propagators/jaeger v1.20.0 h1:iVhNKkMIpzyZqxk8jkDU2n4DFTD+FbpGacvooxEvyyc= +go.opentelemetry.io/contrib/propagators/jaeger v1.20.0/go.mod h1:cpSABr0cm/AH/HhbJjn+AudBVUMgZWdfN3Gb+ZqxSZc= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s= golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -283,17 +296,19 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -309,16 +324,16 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -337,6 +352,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= +gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -345,6 +361,7 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf h1:v5Cf4E9+6tawYrs/grq1q1hFpGtzlGFzgWHqwt6NFiU= +google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf h1:xkVZ5FdZJF4U82Q/JS+DcZA83s/GRVL+QrFMlexk9Yo= google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf h1:guOdSPaeFgN+jEJwTo1dQ71hdBm+yKSCCKuTRkJzcVo= @@ -354,8 +371,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= From fd13920e31c58e9d45a5cd8efc115760a03b701d Mon Sep 17 00:00:00 2001 From: scottlepp Date: Fri, 27 Oct 2023 08:52:55 -0400 Subject: [PATCH 2/6] update go --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 794c7a2..8a604b6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -18,11 +18,11 @@ services: steps: - name: "test" - image: golang:1.20 + image: golang:1.21 commands: - go test ./... - name: "integraiton_tests" - image: golang:1.20 + image: golang:1.21 environment: INTEGRATION_TESTS: "true" MYSQL_URL: "mysql:mysql@tcp(mysql:3306)/mysql" From 21713c5416ea9fd087dd617b0f775a7f22b5c53d Mon Sep 17 00:00:00 2001 From: scottlepp Date: Fri, 27 Oct 2023 09:00:29 -0400 Subject: [PATCH 3/6] update signature --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 8a604b6..9f63012 100644 --- a/.drone.yml +++ b/.drone.yml @@ -31,6 +31,6 @@ steps: --- kind: signature -hmac: a87eed3ff4ff31806a1f22daad4f283b9c6b0e4f8a8be84ae611016a7c4ac474 +hmac: ee935b524c92f8c6eadfef904268c9a9d8ac6f0309d8a46597e5ae2ec49f1995 ... From 22ab7503406f29579b598259e6846306aedafecd Mon Sep 17 00:00:00 2001 From: scottlepp Date: Fri, 27 Oct 2023 15:48:52 -0400 Subject: [PATCH 4/6] cleanup tests --- datasource_connect_test.go | 125 +++++++++ datasource_test.go | 522 ++++++------------------------------- test/driver.go | 61 ++++- 3 files changed, 257 insertions(+), 451 deletions(-) create mode 100644 datasource_connect_test.go diff --git a/datasource_connect_test.go b/datasource_connect_test.go new file mode 100644 index 0000000..a1a9456 --- /dev/null +++ b/datasource_connect_test.go @@ -0,0 +1,125 @@ +package sqlds + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" +) + +type fakeDriver struct { + openDBfn func(msg json.RawMessage) (*sql.DB, error) + + Driver +} + +func (d fakeDriver) Connect(_ context.Context, _ backend.DataSourceInstanceSettings, msg json.RawMessage) (db *sql.DB, err error) { + return d.openDBfn(msg) +} + +func (d fakeDriver) Macros() Macros { + return Macros{} +} + +func (d fakeDriver) Converters() []sqlutil.Converter { + return []sqlutil.Converter{} +} + +func Test_getDBConnectionFromQuery(t *testing.T) { + db := &sql.DB{} + db2 := &sql.DB{} + db3 := &sql.DB{} + d := &fakeDriver{openDBfn: func(msg json.RawMessage) (*sql.DB, error) { return db3, nil }} + tests := []struct { + desc string + dsUID string + args string + existingDB *sql.DB + expectedKey string + expectedDB *sql.DB + }{ + { + desc: "it should return the default db with no args", + dsUID: "uid1", + args: "", + expectedKey: "uid1-default", + expectedDB: db, + }, + { + desc: "it should return the cached connection for the given args", + dsUID: "uid1", + args: "foo", + expectedKey: "uid1-foo", + existingDB: db2, + expectedDB: db2, + }, + { + desc: "it should create a new connection with the given args", + dsUID: "uid1", + args: "foo", + expectedKey: "uid1-foo", + expectedDB: db3, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + ds := &SQLDatasource{c: d, EnableMultipleConnections: true} + settings := backend.DataSourceInstanceSettings{UID: tt.dsUID} + key := defaultKey(tt.dsUID) + // Add the mandatory default db + ds.storeDBConnection(key, dbConnection{db, settings}) + if tt.existingDB != nil { + key = keyWithConnectionArgs(tt.dsUID, []byte(tt.args)) + ds.storeDBConnection(key, dbConnection{tt.existingDB, settings}) + } + + key, dbConn, err := ds.getDBConnectionFromQuery(context.Background(), &Query{ConnectionArgs: json.RawMessage(tt.args)}, tt.dsUID) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if key != tt.expectedKey { + t.Fatalf("unexpected cache key %s", key) + } + if dbConn.db != tt.expectedDB { + t.Fatalf("unexpected result %v", dbConn.db) + } + }) + } + + t.Run("it should return an error if connection args are used without enabling multiple connections", func(t *testing.T) { + ds := &SQLDatasource{c: d, EnableMultipleConnections: false} + _, _, err := ds.getDBConnectionFromQuery(context.Background(), &Query{ConnectionArgs: json.RawMessage("foo")}, "dsUID") + if err == nil || !errors.Is(err, MissingMultipleConnectionsConfig) { + t.Errorf("expecting error: %v", MissingMultipleConnectionsConfig) + } + }) + + t.Run("it should return an error if the default connection is missing", func(t *testing.T) { + ds := &SQLDatasource{c: d} + _, _, err := ds.getDBConnectionFromQuery(context.Background(), &Query{}, "dsUID") + if err == nil || !errors.Is(err, MissingDBConnection) { + t.Errorf("expecting error: %v", MissingDBConnection) + } + }) +} + +func Test_Dispose(t *testing.T) { + t.Run("it should not delete connections", func(t *testing.T) { + ds := &SQLDatasource{} + ds.dbConnections.Store(defaultKey("uid1"), dbConnection{}) + ds.dbConnections.Store("foo", dbConnection{}) + ds.Dispose() + count := 0 + ds.dbConnections.Range(func(key, value interface{}) bool { + count++ + return true + }) + if count != 2 { + t.Errorf("missing connections") + } + }) +} diff --git a/datasource_test.go b/datasource_test.go index ad36da2..525caab 100644 --- a/datasource_test.go +++ b/datasource_test.go @@ -1,277 +1,42 @@ -package sqlds +package sqlds_test import ( "context" - "database/sql" - "database/sql/driver" "encoding/json" "errors" - "fmt" - "io" "testing" - "time" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" - "github.com/grafana/sqlds/v3/mock" + "github.com/grafana/sqlds/v3" + "github.com/grafana/sqlds/v3/test" "github.com/stretchr/testify/assert" ) -type fakeDriver struct { - openDBfn func(msg json.RawMessage) (*sql.DB, error) - - Driver -} - -func (d fakeDriver) Connect(_ context.Context, _ backend.DataSourceInstanceSettings, msg json.RawMessage) (db *sql.DB, err error) { - return d.openDBfn(msg) -} - -func (d fakeDriver) Macros() Macros { - return Macros{} -} - -func (d fakeDriver) Converters() []sqlutil.Converter { - return []sqlutil.Converter{} -} - -func Test_getDBConnectionFromQuery(t *testing.T) { - db := &sql.DB{} - db2 := &sql.DB{} - db3 := &sql.DB{} - d := &fakeDriver{openDBfn: func(msg json.RawMessage) (*sql.DB, error) { return db3, nil }} - tests := []struct { - desc string - dsUID string - args string - existingDB *sql.DB - expectedKey string - expectedDB *sql.DB - }{ - { - desc: "it should return the default db with no args", - dsUID: "uid1", - args: "", - expectedKey: "uid1-default", - expectedDB: db, - }, - { - desc: "it should return the cached connection for the given args", - dsUID: "uid1", - args: "foo", - expectedKey: "uid1-foo", - existingDB: db2, - expectedDB: db2, - }, - { - desc: "it should create a new connection with the given args", - dsUID: "uid1", - args: "foo", - expectedKey: "uid1-foo", - expectedDB: db3, - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - ds := &SQLDatasource{c: d, EnableMultipleConnections: true} - settings := backend.DataSourceInstanceSettings{UID: tt.dsUID} - key := defaultKey(tt.dsUID) - // Add the mandatory default db - ds.storeDBConnection(key, dbConnection{db, settings}) - if tt.existingDB != nil { - key = keyWithConnectionArgs(tt.dsUID, []byte(tt.args)) - ds.storeDBConnection(key, dbConnection{tt.existingDB, settings}) - } - - key, dbConn, err := ds.getDBConnectionFromQuery(context.Background(), &Query{ConnectionArgs: json.RawMessage(tt.args)}, tt.dsUID) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - if key != tt.expectedKey { - t.Fatalf("unexpected cache key %s", key) - } - if dbConn.db != tt.expectedDB { - t.Fatalf("unexpected result %v", dbConn.db) - } - }) - } - - t.Run("it should return an error if connection args are used without enabling multiple connections", func(t *testing.T) { - ds := &SQLDatasource{c: d, EnableMultipleConnections: false} - _, _, err := ds.getDBConnectionFromQuery(context.Background(), &Query{ConnectionArgs: json.RawMessage("foo")}, "dsUID") - if err == nil || !errors.Is(err, MissingMultipleConnectionsConfig) { - t.Errorf("expecting error: %v", MissingMultipleConnectionsConfig) - } - }) - - t.Run("it should return an error if the default connection is missing", func(t *testing.T) { - ds := &SQLDatasource{c: d} - _, _, err := ds.getDBConnectionFromQuery(context.Background(), &Query{}, "dsUID") - if err == nil || !errors.Is(err, MissingDBConnection) { - t.Errorf("expecting error: %v", MissingDBConnection) - } - }) -} - -func Test_Dispose(t *testing.T) { - t.Run("it should not delete connections", func(t *testing.T) { - ds := &SQLDatasource{} - ds.dbConnections.Store(defaultKey("uid1"), dbConnection{}) - ds.dbConnections.Store("foo", dbConnection{}) - ds.Dispose() - count := 0 - ds.dbConnections.Range(func(key, value interface{}) bool { - count++ - return true - }) - if count != 2 { - t.Errorf("missing connections") - } - }) -} - -func Test_timeout_retries(t *testing.T) { - dsUID := "timeout" - settings := backend.DataSourceInstanceSettings{UID: dsUID} - - handler := &testSqlHandler{} - mockDriver := "sqlmock" - mock.RegisterDriver(mockDriver, handler) - db, err := sql.Open(mockDriver, "") - if err != nil { - t.Errorf("failed to connect to mock driver: %v", err) - } - timeoutDriver := fakeDriver{ - openDBfn: func(msg json.RawMessage) (*sql.DB, error) { - db, err := sql.Open(mockDriver, "") - if err != nil { - t.Errorf("failed to connect to mock driver: %v", err) - } - return db, nil - }, +func Test_health_retries(t *testing.T) { + opts := test.DriverOpts{ + ConnectError: errors.New("foo"), } - retries := 5 - max := time.Duration(testTimeout) * time.Second - driverSettings := DriverSettings{Retries: retries, Timeout: max, RetryOn: []string{"deadline"}} - ds := &SQLDatasource{c: timeoutDriver, driverSettings: driverSettings} + cfg := `{ "timeout": 0, "retries": 5, "retryOn": ["foo"] }` + req, handler, ds := healthRequest(t, "timeout", opts, cfg) - key := defaultKey(dsUID) - // Add the mandatory default db - ds.storeDBConnection(key, dbConnection{db, settings}) - ctx := context.Background() - req := &backend.CheckHealthRequest{ - PluginContext: backend.PluginContext{ - DataSourceInstanceSettings: &settings, - }, - } - result, err := ds.CheckHealth(ctx, req) + _, err := ds.CheckHealth(context.Background(), &req) - assert.Nil(t, err) - assert.Equal(t, retries, testCounter) - expected := context.DeadlineExceeded.Error() - assert.Equal(t, expected, result.Message) + assert.Equal(t, nil, err) + assert.Equal(t, 5, handler.State.ConnectAttempts) } -func Test_error_retries(t *testing.T) { - testCounter = 0 - dsUID := "error" - settings := backend.DataSourceInstanceSettings{UID: dsUID} - - handler := &testSqlHandler{ - error: errors.New("foo"), +func Test_query_retries(t *testing.T) { + cfg := `{ "timeout": 0, "retries": 5, "retryOn": ["foo"] }` + opts := test.DriverOpts{ + QueryError: errors.New("foo"), } - mockDriver := "sqlmock-error" - mock.RegisterDriver(mockDriver, handler) - timeoutDriver := fakeDriver{ - openDBfn: func(msg json.RawMessage) (*sql.DB, error) { - db, err := sql.Open(mockDriver, "") - if err != nil { - t.Errorf("failed to connect to mock driver: %v", err) - } - return db, nil - }, - } - retries := 5 - max := time.Duration(10) * time.Second - driverSettings := DriverSettings{Retries: retries, Timeout: max, Pause: 1, RetryOn: []string{"foo"}} - ds := &SQLDatasource{c: timeoutDriver, driverSettings: driverSettings} - - key := defaultKey(dsUID) - // Add the mandatory default db - db, _ := timeoutDriver.Connect(context.Background(), settings, nil) - ds.storeDBConnection(key, dbConnection{db, settings}) - ctx := context.Background() - - qry := `{ "rawSql": "foo" }` - - req := &backend.QueryDataRequest{ - PluginContext: backend.PluginContext{ - DataSourceInstanceSettings: &settings, - }, - Queries: []backend.DataQuery{ - { - RefID: "foo", - JSON: []byte(qry), - }, - }, - } - - data, err := ds.QueryData(ctx, req) - assert.Nil(t, err) - assert.Equal(t, retries+1, testCounter) - assert.NotNil(t, data.Responses) - -} - -func Test_error_source(t *testing.T) { - testCounter = 0 - dsUID := "error" - settings := backend.DataSourceInstanceSettings{UID: dsUID} - - handler := &testSqlHandler{ - error: errors.New("foo"), - } - mockDriver := "sqlmock-error-source" - mock.RegisterDriver(mockDriver, handler) - - errorDriver := fakeDriver{ - openDBfn: func(msg json.RawMessage) (*sql.DB, error) { - db, err := sql.Open(mockDriver, "") - if err != nil { - t.Errorf("failed to connect to mock driver: %v", err) - } - return db, errors.New("foo") - }, - } - retries := 0 - max := time.Duration(10) * time.Second - driverSettings := DriverSettings{Retries: retries, Timeout: max, Pause: 1, RetryOn: []string{"foo"}} - ds := &SQLDatasource{c: errorDriver, driverSettings: driverSettings} - - key := defaultKey(dsUID) - // Add the mandatory default db - db, _ := errorDriver.Connect(context.Background(), settings, nil) - ds.storeDBConnection(key, dbConnection{db, settings}) - ctx := context.Background() - - qry := `{ "rawSql": "foo" }` - - req := &backend.QueryDataRequest{ - PluginContext: backend.PluginContext{ - DataSourceInstanceSettings: &settings, - }, - Queries: []backend.DataQuery{ - { - RefID: "foo", - JSON: []byte(qry), - }, - }, - } + req, handler, ds := queryRequest(t, "error", opts, cfg) - data, err := ds.QueryData(ctx, req) + data, err := ds.QueryData(context.Background(), req) assert.Nil(t, err) assert.NotNil(t, data.Responses) + assert.Equal(t, 6, handler.State.QueryAttempts) res := data.Responses["foo"] assert.NotNil(t, res.Error) @@ -279,229 +44,104 @@ func Test_error_source(t *testing.T) { } func Test_query_apply_headers(t *testing.T) { - testCounter = 0 - dsUID := "headers" - settings := backend.DataSourceInstanceSettings{UID: dsUID} - - // first query will fail since the connection is missing tokens - handler := &testSqlHandler{ - error: errors.New("missing token"), + var message []byte + onConnect := func(msg []byte) { + message = msg } - mockDriver := "sqlmock-query-error" - mock.RegisterDriver(mockDriver, handler) - opened := false - var message json.RawMessage - timeoutDriver := fakeDriver{ - openDBfn: func(msg json.RawMessage) (*sql.DB, error) { - if opened { - // second query attempt will have tokens and won't return an error - handler = &testSqlHandler{} - mockDriver = "sqlmock-query-token" - mock.RegisterDriver(mockDriver, handler) - } - db, err := sql.Open(mockDriver, "") - if err != nil { - t.Errorf("failed to connect to mock driver: %v", err) - } - opened = true - message = msg - return db, nil - }, + opts := test.DriverOpts{ + QueryError: errors.New("missing token"), + QueryFailTimes: 1, // first check always fails since headers are not available on initial connect + OnConnect: onConnect, } - max := time.Duration(10) * time.Second - // retry once for token errors since the first connection will not have the token and throw a connection error - driverSettings := DriverSettings{Retries: 1, Timeout: max, Pause: 1, RetryOn: []string{"token"}, ForwardHeaders: true} - ds := &SQLDatasource{c: timeoutDriver, driverSettings: driverSettings} + cfg := `{ "timeout": 0, "retries": 1, "retryOn": ["missing token"], "forwardHeaders": true }` - key := defaultKey(dsUID) - // Add the mandatory default db - db, _ := timeoutDriver.Connect(context.Background(), settings, nil) - ds.storeDBConnection(key, dbConnection{db, settings}) - ctx := context.Background() + req, handler, ds := queryRequest(t, "headers", opts, cfg) - qry := `{ "rawSql": "foo" }` - - req := &backend.QueryDataRequest{ - PluginContext: backend.PluginContext{ - DataSourceInstanceSettings: &settings, - }, - Queries: []backend.DataQuery{ - { - RefID: "foo", - JSON: []byte(qry), - }, - }, - } - req.SetHTTPHeader("hey", "scott") + req.SetHTTPHeader("foo", "bar") - data, err := ds.QueryData(ctx, req) + data, err := ds.QueryData(context.Background(), req) assert.Nil(t, err) assert.NotNil(t, data.Responses) + assert.Equal(t, 2, handler.State.QueryAttempts) - res := data.Responses["foo"] - assert.Nil(t, res.Error) - - assert.Contains(t, string(message), "scott") + assert.Contains(t, string(message), "bar") } func Test_check_health_with_headers(t *testing.T) { - dsUID := "headers" - settings := backend.DataSourceInstanceSettings{UID: dsUID} - - // first check will fail since the connection is missing tokens - handler := &testSqlHandler{ - error: errors.New("missing token"), - } - mockDriver := "sqlmock-header-error" - mock.RegisterDriver(mockDriver, handler) - - opened := false var message json.RawMessage - timeoutDriver := fakeDriver{ - openDBfn: func(msg json.RawMessage) (*sql.DB, error) { - if opened { - // second query attempt will have tokens and won't return an error - handler = &testSqlHandler{ - ping: true, - checks: handler.checks, - } - mockDriver = "sqlmock-header-token" - mock.RegisterDriver(mockDriver, handler) - } - db, err := sql.Open(mockDriver, "") - if err != nil { - t.Errorf("failed to connect to mock driver: %v", err) - } - opened = true - message = msg - return db, nil - }, + onConnect := func(msg []byte) { + message = msg } - max := time.Duration(10) * time.Second - // retry once for token errors since the first connection will not have the token and throw a connection error - driverSettings := DriverSettings{Retries: 1, Timeout: max, Pause: 1, RetryOn: []string{"token"}, ForwardHeaders: true} - ds := &SQLDatasource{c: timeoutDriver, driverSettings: driverSettings} - - key := defaultKey(dsUID) - // Add the mandatory default db - db, _ := timeoutDriver.Connect(context.Background(), settings, nil) - ds.storeDBConnection(key, dbConnection{db, settings}) - ctx := context.Background() - - headers := map[string]string{} - headers["foo"] = "bar" - req := &backend.CheckHealthRequest{ - PluginContext: backend.PluginContext{ - DataSourceInstanceSettings: &settings, - }, - Headers: headers, + opts := test.DriverOpts{ + ConnectError: errors.New("missing token"), + ConnectFailTimes: 1, // first check always fails since headers are not available on initial connect + OnConnect: onConnect, } + cfg := `{ "timeout": 0, "retries": 2, "retryOn": ["missing token"], "forwardHeaders": true }` + req, handler, ds := healthRequest(t, "timeout", opts, cfg) + r := &req + r.SetHTTPHeader("foo", "bar") - req.SetHTTPHeader("foo", "bar") - - res, err := ds.CheckHealth(ctx, req) + res, err := ds.CheckHealth(context.Background(), r) assert.Nil(t, err) assert.Equal(t, "Data source is working", res.Message) - + assert.Equal(t, 2, handler.State.ConnectAttempts) assert.Contains(t, string(message), "bar") } func Test_no_errors(t *testing.T) { - dsUID := "pass" - settings := backend.DataSourceInstanceSettings{UID: dsUID} - - handler := &testSqlHandler{pass: true} - mockDriver := "no-errors" - mock.RegisterDriver(mockDriver, handler) - db, err := sql.Open(mockDriver, "") - if err != nil { - t.Errorf("failed to connect to mock driver: %v", err) - } - driver := fakeDriver{ - openDBfn: func(msg json.RawMessage) (*sql.DB, error) { - db, err := sql.Open(mockDriver, "") - if err != nil { - t.Errorf("failed to connect to mock driver: %v", err) - } - return db, nil - }, - } - max := time.Duration(10) * time.Second - driverSettings := DriverSettings{Retries: 1, Timeout: max, RetryOn: []string{""}, Errors: true} - ds := &SQLDatasource{c: driver, driverSettings: driverSettings} - - key := defaultKey(dsUID) - // Add the mandatory default db - ds.storeDBConnection(key, dbConnection{db, settings}) - ctx := context.Background() - req := &backend.CheckHealthRequest{ - PluginContext: backend.PluginContext{ - DataSourceInstanceSettings: &settings, - }, - } - result, err := ds.CheckHealth(ctx, req) + req, _, ds := healthRequest(t, "pass", test.DriverOpts{}, "{}") + result, err := ds.CheckHealth(context.Background(), &req) assert.Nil(t, err) expected := "Data source is working" assert.Equal(t, expected, result.Message) } -var testCounter = 0 -var testTimeout = 1 -var testRows = 0 +func queryRequest(t *testing.T, name string, opts test.DriverOpts, cfg string) (*backend.QueryDataRequest, *test.SqlHandler, *sqlds.SQLDatasource) { + driver, handler := test.NewDriver(name, test.Data{}, nil, opts) + ds := sqlds.NewDatasource(driver) -type testSqlHandler struct { - mock.DBHandler - error - ping bool - checks int - pass bool -} + req, settings := setupQueryRequest(name, cfg) -func (s *testSqlHandler) Ping(ctx context.Context) error { - s.checks += 1 - if s.pass { - return nil - } - if s.error != nil { - return s.error - } - if s.ping { - return nil - } - testCounter++ // track the retries for the test assertion - time.Sleep(time.Duration(testTimeout + 1)) // simulate a connection delay - select { - case <-ctx.Done(): - fmt.Println(ctx.Err()) - return ctx.Err() - } + _, err := ds.NewDatasource(context.Background(), settings) + assert.Equal(t, nil, err) + return req, handler, ds } -func (s *testSqlHandler) Query(args []driver.Value) (driver.Rows, error) { - fmt.Println("query") - if s.error != nil { - testCounter++ - return s, s.error - } - return s, nil +func setupQueryRequest(id string, cfg string) (*backend.QueryDataRequest, backend.DataSourceInstanceSettings) { + s := backend.DataSourceInstanceSettings{UID: id, JSONData: []byte(cfg)} + return &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{ + DataSourceInstanceSettings: &s, + }, + Queries: []backend.DataQuery{ + { + RefID: "foo", + JSON: []byte(`{ "rawSql": "foo" }`), + }, + }, + }, s } -func (s *testSqlHandler) Columns() []string { - return []string{"foo", "bar"} -} +func healthRequest(t *testing.T, name string, opts test.DriverOpts, cfg string) (backend.CheckHealthRequest, *test.SqlHandler, *sqlds.SQLDatasource) { + driver, handler := test.NewDriver(name, test.Data{}, nil, opts) + ds := sqlds.NewDatasource(driver) -func (s *testSqlHandler) Next(dest []driver.Value) error { - testRows++ - if testRows > 5 { - return io.EOF - } - dest[0] = "foo" - dest[1] = "bar" - return nil + req, settings := setupHealthRequest(name, cfg) + + _, err := ds.NewDatasource(context.Background(), settings) + assert.Equal(t, nil, err) + return req, handler, ds } -func (s *testSqlHandler) Close() error { - return nil +func setupHealthRequest(id string, cfg string) (backend.CheckHealthRequest, backend.DataSourceInstanceSettings) { + settings := backend.DataSourceInstanceSettings{UID: id, JSONData: []byte(cfg)} + req := backend.CheckHealthRequest{ + PluginContext: backend.PluginContext{ + DataSourceInstanceSettings: &settings, + }, + } + return req, settings } diff --git a/test/driver.go b/test/driver.go index e9a7e80..a83c48b 100644 --- a/test/driver.go +++ b/test/driver.go @@ -4,10 +4,13 @@ import ( "context" "database/sql" "database/sql/driver" + "encoding/json" + "fmt" "io" "reflect" "time" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" "github.com/grafana/sqlds/v3" "github.com/grafana/sqlds/v3/mock" @@ -24,7 +27,10 @@ func NewDriver(name string, dbdata Data, converters []sqlutil.Converter, opts Dr } return NewTestDS( - func() (*sql.DB, error) { + func(msg json.RawMessage) (*sql.DB, error) { + if opts.OnConnect != nil { + opts.OnConnect(msg) + } return sql.Open(name, "") }, converters, @@ -32,7 +38,7 @@ func NewDriver(name string, dbdata Data, converters []sqlutil.Converter, opts Dr } // NewTestDS creates a new test datasource driver -func NewTestDS(openDBfn func() (*sql.DB, error), converters []sqlutil.Converter) TestDS { +func NewTestDS(openDBfn func(msg json.RawMessage) (*sql.DB, error), converters []sqlutil.Converter) TestDS { return TestDS{ openDBfn: openDBfn, converters: converters, @@ -62,7 +68,7 @@ func (s *SqlHandler) Ping(ctx context.Context) error { if s.Opts.ConnectDelay > 0 { time.Sleep(time.Duration(s.Opts.ConnectDelay * int(time.Second))) // simulate a connection delay } - if s.Opts.ConnectError != nil { + if s.Opts.ConnectError != nil && (s.Opts.ConnectFailTimes == 0 || s.State.ConnectAttempts <= s.Opts.ConnectFailTimes) { return s.Opts.ConnectError } return nil @@ -138,23 +144,50 @@ type Column struct { // TestDS ... type TestDS struct { - openDBfn func() (*sql.DB, error) + openDBfn func(msg json.RawMessage) (*sql.DB, error) converters []sqlutil.Converter sqlds.Driver } // Open - opens the test database func (s TestDS) Open() (*sql.DB, error) { - return s.openDBfn() + return s.openDBfn(nil) +} + +// Connect - connects to the test database +func (s TestDS) Connect(ctx context.Context, cfg backend.DataSourceInstanceSettings, msg json.RawMessage) (*sql.DB, error) { + return s.openDBfn(msg) +} + +// Settings - Settings to the test database +func (s TestDS) Settings(ctx context.Context, config backend.DataSourceInstanceSettings) sqlds.DriverSettings { + settings, err := LoadSettings(ctx, config) + if err != nil { + fmt.Println("error loading settings") + return sqlds.DriverSettings{} + } + return settings +} + +// Macros - Macros for the test database +func (s TestDS) Macros() sqlds.Macros { + return sqlds.DefaultMacros +} + +// Converters - Converters for the test database +func (s TestDS) Converters() []sqlutil.Converter { + return nil } // DriverOpts the optional settings type DriverOpts struct { - ConnectDelay int - QueryDelay int - ConnectError error - QueryError error - QueryFailTimes int + ConnectDelay int + ConnectError error + ConnectFailTimes int + OnConnect func(msg []byte) + QueryDelay int + QueryError error + QueryFailTimes int } // State is the state of the connections/queries @@ -162,3 +195,11 @@ type State struct { QueryAttempts int ConnectAttempts int } + +// LoadSettings will read and validate Settings from the DataSourceConfig +func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings) (settings sqlds.DriverSettings, err error) { + if err := json.Unmarshal(config.JSONData, &settings); err != nil { + return settings, fmt.Errorf("%s: %s", err.Error(), "Invalid Settings") + } + return settings, nil +} From 4678d9670a13ef6aa763b0b8fa7cf05910126af3 Mon Sep 17 00:00:00 2001 From: scottlepp Date: Fri, 27 Oct 2023 15:51:59 -0400 Subject: [PATCH 5/6] fix test --- datasource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datasource_test.go b/datasource_test.go index 525caab..00b9fc4 100644 --- a/datasource_test.go +++ b/datasource_test.go @@ -79,7 +79,7 @@ func Test_check_health_with_headers(t *testing.T) { OnConnect: onConnect, } cfg := `{ "timeout": 0, "retries": 2, "retryOn": ["missing token"], "forwardHeaders": true }` - req, handler, ds := healthRequest(t, "timeout", opts, cfg) + req, handler, ds := healthRequest(t, "health-headers", opts, cfg) r := &req r.SetHTTPHeader("foo", "bar") From c9d3df81af2b0c0e30bbb67758f76d66ecb32c87 Mon Sep 17 00:00:00 2001 From: scottlepp Date: Fri, 27 Oct 2023 15:54:47 -0400 Subject: [PATCH 6/6] cleanup --- errors.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/errors.go b/errors.go index 04a9e54..281b4e7 100644 --- a/errors.go +++ b/errors.go @@ -28,8 +28,6 @@ func DownstreamError(err error, override ...bool) error { return es.DownstreamError(err, len(override) > 0) } -type Error es.Error - func ErrorSource(err error) backend.ErrorSource { var se es.Error if errors.As(err, &se) {