diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fee3c1ec2b4..1a3db25c241 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -53,9 +53,10 @@ go.sum @grafana/backend-platform /pkg/services/sqlstore/migrations @grafana/backend-platform @grafana/hosted-grafana-team *_mig.go @grafana/backend-platform @grafana/hosted-grafana-team -# Grafana live +# Grafana edge /pkg/services/live/ @grafana/grafana-edge-squad /pkg/services/searchV2/ @grafana/grafana-edge-squad +/pkg/infra/filestore/ @grafana/grafana-edge-squad # Alerting /pkg/services/ngalert @grafana/alerting-squad-backend diff --git a/go.mod b/go.mod index 19c0d32aa94..04e2a981048 100644 --- a/go.mod +++ b/go.mod @@ -14,16 +14,16 @@ replace k8s.io/client-go => k8s.io/client-go v0.22.1 replace github.com/russellhaering/goxmldsig@v1.1.0 => github.com/russellhaering/goxmldsig v1.1.1 require ( - cloud.google.com/go/storage v1.14.0 + cloud.google.com/go/storage v1.18.2 cuelang.org/go v0.4.0 - github.com/Azure/azure-sdk-for-go v57.1.0+incompatible + github.com/Azure/azure-sdk-for-go v59.3.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.10.0 - github.com/Azure/go-autorest/autorest v0.11.20 + github.com/Azure/go-autorest/autorest v0.11.22 github.com/BurntSushi/toml v0.3.1 github.com/Masterminds/semver v1.5.0 github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f - github.com/aws/aws-sdk-go v1.40.37 + github.com/aws/aws-sdk-go v1.42.8 github.com/beevik/etree v1.1.0 github.com/benbjohnson/clock v1.1.0 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b @@ -31,7 +31,7 @@ require ( github.com/cortexproject/cortex v1.10.1-0.20211014125347-85c378182d0d github.com/crewjam/saml v0.4.6-0.20210521115923-29c6295245bd github.com/davecgh/go-spew v1.1.1 - github.com/denisenkom/go-mssqldb v0.10.0 + github.com/denisenkom/go-mssqldb v0.11.0 github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 github.com/fatih/color v1.10.0 github.com/gchaincl/sqlhooks v1.3.0 @@ -66,7 +66,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/jung-kurt/gofpdf v1.16.2 github.com/laher/mergefs v0.1.1 - github.com/lib/pq v1.10.0 + github.com/lib/pq v1.10.4 github.com/linkedin/goavro/v2 v2.10.0 github.com/m3db/prometheus_remote_client_golang v0.4.4 github.com/magefile/mage v1.12.1 @@ -103,16 +103,16 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.0.0 go.opentelemetry.io/otel/sdk v1.0.0 go.opentelemetry.io/otel/trace v1.2.0 - golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e - golang.org/x/exp v0.0.0-20210220032938-85be41e4509f // indirect - golang.org/x/net v0.0.0-20211013171255-e13a2654a71e - golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f + golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 + golang.org/x/exp v0.0.0-20210220032938-85be41e4509f + golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac + golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 golang.org/x/tools v0.1.5 gonum.org/v1/gonum v0.9.3 - google.golang.org/api v0.58.0 - google.golang.org/grpc v1.41.0 + google.golang.org/api v0.60.0 + google.golang.org/grpc v1.42.0 google.golang.org/protobuf v1.27.1 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.62.0 @@ -168,14 +168,14 @@ require ( github.com/go-openapi/errors v0.20.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/loads v0.20.2 // indirect + github.com/go-openapi/loads v0.20.2 github.com/go-openapi/runtime v0.19.29 // indirect - github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/spec v0.20.4 github.com/go-openapi/swag v0.19.15 // indirect github.com/go-openapi/validate v0.20.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/status v1.1.0 // indirect - github.com/golang-jwt/jwt/v4 v4.0.0 // indirect + github.com/golang-jwt/jwt/v4 v4.1.0 // indirect github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -207,7 +207,7 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/miekg/dns v1.1.43 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/mna/redisc v1.3.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -242,27 +242,32 @@ require ( go.mongodb.org/mongo-driver v1.7.0 // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 - go.uber.org/goleak v1.1.10 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2 // indirect + google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect ) require ( cloud.google.com/go/kms v1.1.0 github.com/golang-migrate/migrate/v4 v4.7.0 + gocloud.dev v0.24.0 ) require ( - github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.17 // indirect + github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect + github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect + github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect github.com/containerd/containerd v1.5.9 // indirect + github.com/envoyproxy/go-control-plane v0.10.1 // indirect + github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/klauspost/compress v1.13.1 // indirect + github.com/klauspost/compress v1.13.6 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.8 // indirect github.com/segmentio/asm v1.1.1 // indirect @@ -274,3 +279,7 @@ replace github.com/crewjam/saml => github.com/grafana/saml v0.0.0-20211007135653 replace github.com/apache/thrift => github.com/apache/thrift v0.14.1 replace github.com/hashicorp/consul => github.com/hashicorp/consul v1.10.2 + +// TODO: remove once gocloud.dev releases 0.25.x +// `fileblob` implementation has buggy key ordering in 0.24.0 +replace gocloud.dev v0.24.0 => github.com/google/go-cloud v0.24.1-0.20220209172924-99801bbb523a diff --git a/go.sum b/go.sum index 67d93cc8c4c..b409fd39e11 100644 --- a/go.sum +++ b/go.sum @@ -20,10 +20,10 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= @@ -44,25 +44,33 @@ cloud.google.com/go/bigtable v1.3.0/go.mod h1:z5EyKrPE8OQmeg4h5MNdKvuSnI9CCT49Ki cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/kms v1.0.0/go.mod h1:nhUehi+w7zht2XrUfvTRNpxrfayBHqP4lu2NSywui/0= cloud.google.com/go/kms v1.1.0 h1:1yc4rLqCkVDS9Zvc7m+3mJ47kw0Uo5Q5+sMjcmUVUeM= cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI= +cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.17.1/go.mod h1:4qDxMr1WsM9+aQAz36ltDwCIM+R0QdlseyFjBuNvnss= +cloud.google.com/go/secretmanager v1.0.0/go.mod h1:+Qkm5qxIJ5mk74xxIXA+87fseaY1JLYBcFPQoc/GQxg= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.3.0/go.mod h1:9IAwXhoyBJ7z9LcAwkj0/7NnPzYaPeZxxVp3zm+5IqA= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.18.2 h1:5NQw6tOn3eMm0oE8vTkfjau18kjL79FlMjy/CHTpmoY= +cloud.google.com/go/storage v1.18.2/go.mod h1:AiIj7BWXyhO5gGVmYJ+S8tbkCx3yb0IMjua8Aw4naVM= +cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A= code.cloudfoundry.org/clock v1.0.0/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs= contrib.go.opencensus.io/exporter/prometheus v0.3.0/go.mod h1:rpCPVQKhiyH8oomWgm34ZmgIdZa8OVYO5WAIygPbBBE= +contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8= +contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= cuelang.org/go v0.4.0 h1:GLJblw6m2WGGCA3k1v6Wbk9gTOt2qto48ahO2MmSd6I= cuelang.org/go v0.4.0/go.mod h1:tz/edkPi+T37AZcb5GlPY+WJkL6KiDlDVupKwL3vvjs= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -70,6 +78,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7 gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-amqp-common-go/v3 v3.0.0/go.mod h1:SY08giD/XbhTz07tJdpw1SoxQXHPN30+DI3Z04SYqyg= +github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI= +github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI= github.com/Azure/azure-event-hubs-go/v3 v3.2.0/go.mod h1:BPIIJNH/l/fVHYq3Rm6eg4clbrULrQ3q7+icmqHyyLc= github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= @@ -88,23 +98,29 @@ github.com/Azure/azure-sdk-for-go v44.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo github.com/Azure/azure-sdk-for-go v45.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v48.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v51.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v52.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v54.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v55.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v57.1.0+incompatible h1:TKQ3ieyB0vVKkF6t9dsWbMjq56O1xU3eh3Ec09v6ajM= github.com/Azure/azure-sdk-for-go v57.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v59.3.0+incompatible h1:dPIm0BO4jsMXFcCI/sLTPkBtE7mk8WMuRHA0JeWhlcQ= +github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 h1:lhSJz9RMbJcTgxifR1hUNJnn6CNYtbgEDtQV22/9RBA= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.10.0 h1:jq5Urf8QJK6h0wr8CMiwggo4OSMkXwpArQlkSjSpaBk= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.10.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0 h1:v9p9TfTbf7AwNb5NYQt7hI41IfPoLFiFkLtb+bmGjT0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= +github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU= github.com/Azure/azure-storage-blob-go v0.6.0/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y= github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs= +github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= github.com/Azure/azure-storage-queue-go v0.0.0-20181215014128-6ed74e755687/go.mod h1:K6am8mT+5iFXgingS9LUc7TmbsW6XBw3nxaRyaMyWc8= github.com/Azure/go-amqp v0.12.6/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo= +github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= +github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= @@ -123,8 +139,9 @@ github.com/Azure/go-autorest/autorest v0.11.11/go.mod h1:eipySxLmqSyC5s5k1CLupqe github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= +github.com/Azure/go-autorest/autorest v0.11.22 h1:bXiQwDjrRmBQOE67bwlvUKAC1EU1yZTPQ38c+bstZws= +github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.1-0.20191028180845-3492b2aff503/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= @@ -137,12 +154,15 @@ github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyC github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk= github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/adal v0.9.17 h1:esOPl2dhcz9P3jqBSJ8tPGEj2EqzPPT6zfyuloiogKY= +github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU= github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.4/go.mod h1:yAQ2b6eP/CmLPnmLvxtT1ALIY3OR1oFcCqVBi8vHiTc= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= @@ -183,6 +203,7 @@ github.com/FZambia/eagle v0.0.1 h1:FN1yTkPihMb5nE8SrlRjoCf7T9H9bTKJFQOm6ach2YU= github.com/FZambia/eagle v0.0.1/go.mod h1:xq6u/JeNZ5/8mrAQ76MMhzNTodASh9FavQlCgg4j48w= github.com/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8= github.com/FZambia/sentinel v1.1.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= +github.com/GoogleCloudPlatform/cloudsql-proxy v1.27.0/go.mod h1:bn9iHmAjogMoIPkqBGyJ9R1m9cXGCjBE/cuhBs3oEsQ= github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= github.com/HdrHistogram/hdrhistogram-go v1.0.1/go.mod h1:BWJ+nMSHY3L41Zj7CA3uXnloDp7xxV0YvstAE7nKTaM= github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I= @@ -306,6 +327,7 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.22.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -324,18 +346,57 @@ github.com/aws/aws-sdk-go v1.34.34/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/ github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.35.30/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.35.31/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.37.8/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.3/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.60/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.40.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/aws/aws-sdk-go v1.40.37 h1:I+Q6cLctkFyMMrKukcDnj+i2kjrQ37LGiOM6xmsxC48= github.com/aws/aws-sdk-go v1.40.37/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go v1.42.8 h1:Tj2RP4Fas1mYchwbmw0qWLJIEATAseyp5iTa1D+LWYQ= +github.com/aws/aws-sdk-go v1.42.8/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4= +github.com/aws/aws-sdk-go-v2 v1.11.0 h1:HxyD62DyNhCfiFGUHqJ/xITD6rAjJ7Dm/2nLxLmO4Ag= +github.com/aws/aws-sdk-go-v2 v1.11.0/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 h1:yVUAwvJC/0WNPbyl0nA3j1L6CW1CN8wBubCRqtG7JLI= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0/go.mod h1:Xn6sxgRuIDflLRJFj5Ev7UxABIkNbccFPV/p8itDReM= +github.com/aws/aws-sdk-go-v2/config v1.10.1 h1:z/ViqIjW6ZeuLWgTWMTSyZzaVWo/1cWeVf1Uu+RF01E= +github.com/aws/aws-sdk-go-v2/config v1.10.1/go.mod h1:auIv5pIIn3jIBHNRcVQcsczn6Pfa6Dyv80Fai0ueoJU= +github.com/aws/aws-sdk-go-v2/credentials v1.6.1 h1:A39JYth2fFCx+omN/gib/jIppx3rRnt2r7UKPq7Mh5Y= +github.com/aws/aws-sdk-go-v2/credentials v1.6.1/go.mod h1:QyvQk1IYTqBWSi1T6UgT/W8DMxBVa5pVuLFSRLLhGf8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.0 h1:OpZjuUy8Jt3CA1WgJgBC5Bz+uOjE5Ppx4NFTRaooUuA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.0/go.mod h1:5E1J3/TTYy6z909QNR0QnXGBpfESYGDqd3O0zqONghU= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.1 h1:p9Dys1g2YdaqMalnp6AwCA+tpMMdJNGw5YYKP/u3sUk= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.1/go.mod h1:wN/mvkow08GauDwJ70jnzJ1e+hE+Q3Q7TwpYLXOe9oI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.0 h1:zY8cNmbBXt3pzjgWgdIbzpQ6qxoCwt+Nx9JbrAf2mbY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.0/go.mod h1:NO3Q5ZTTQtO2xIg2+xTXYDiT7knSejfeDm7WGDaOo0U= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.0 h1:Z3aR/OXBnkYK9zXkNkfitHX6SmUBzSsx8VMHbH4Lvhw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.0/go.mod h1:anlUzBoEWglcUxUQwZA7HQOEVEnQALVZsizAapB2hq8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.0 h1:c10Z7fWxtJCoyc8rv06jdh9xrKnu7bAJiRaKWvTb2mU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.0/go.mod h1:6oXGy4GLpypD3uCh8wcqztigGgmhLToMfjavgh+VySg= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.5.0/go.mod h1:acH3+MQoiMzozT/ivU+DbRg7Ooo2298RdRaWcOv+4vM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 h1:lPLbw4Gn59uoKqvOfSnkJr54XWk5Ak1NK20ZEiSWb3U= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0/go.mod h1:80NaCIH9YU3rzTTs/J/ECATjXuRqzo/wB6ukO6MZ0XY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.0 h1:qGZWS/WgiFY+Zgad2u0gwBHpJxz6Ne401JE7iQI1nKs= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.0/go.mod h1:Mq6AEc+oEjCUlBuLiK5YwW4shSOAKCQ3tXN0sQeYoBA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.0 h1:0BOlTqnNnrEO04oYKzDxMMe68t107pmIotn18HtVonY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.0/go.mod h1:xKCZ4YFSF2s4Hnb/J0TLeOsKuGzICzcElaOKNGrVnx4= +github.com/aws/aws-sdk-go-v2/service/kms v1.10.0/go.mod h1:ZkHWL8m5Nw1g9yMXqpCjnIJtSDToAmNbXXZ9gj0bO7s= +github.com/aws/aws-sdk-go-v2/service/s3 v1.19.0 h1:5mRAms4TjSTOGYsqKYte5kHr1PzpMJSyLThjF3J+hw0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.19.0/go.mod h1:Gwz3aVctJe6mUY9T//bcALArPUaFmNAy2rTB9qN4No8= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.10.0/go.mod h1:qAgsrzF3Z2vvV01j79fs7D75ofCMQe81/OKBJx0rjFY= +github.com/aws/aws-sdk-go-v2/service/sns v1.11.0/go.mod h1:LIPf3BTbSY5UeVli+x/1y2Qw1w8T9DYyp7p18Qt8Zc8= +github.com/aws/aws-sdk-go-v2/service/sqs v1.12.0/go.mod h1:TDqDmQnsbgL2ZMIGUf3z9xTzCMqFX7FP1geAgIlYqvA= +github.com/aws/aws-sdk-go-v2/service/ssm v1.15.0/go.mod h1:kJa2uHklY03rKsNSbEsToeUgWJ1PambXBtRNacorRhg= +github.com/aws/aws-sdk-go-v2/service/sso v1.6.0 h1:JDgKIUZOmLFu/Rv6zXLrVTWCmzA0jcTdvsT8iFIKrAI= +github.com/aws/aws-sdk-go-v2/service/sso v1.6.0/go.mod h1:Q/l0ON1annSU+mc0JybDy1Gy6dnJxIcWjphO6qJPzvM= +github.com/aws/aws-sdk-go-v2/service/sts v1.10.0 h1:1jh8J+JjYRp+QWKOsaZt7rGUgoyrqiiVwIm+w0ymeUw= +github.com/aws/aws-sdk-go-v2/service/sts v1.10.0/go.mod h1:jLKCFqS+1T4i7HDqCP9GM4Uk75YW1cS0o82LdxpMyOE= github.com/aws/smithy-go v1.5.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/smithy-go v1.9.0 h1:c7FUdEqrQA1/UVKKCNDFQPNKGp4FQg3YW4Ck5SLTG58= +github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= @@ -395,6 +456,7 @@ github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/centrifugal/centrifuge v0.19.0 h1:YHws0dRpgsBiI73tRl1wwaB13gzuaI1AM4IFcQQQqcw= github.com/centrifugal/centrifuge v0.19.0/go.mod h1:O2elf8q3Qkie3z97wkqVqxB52pnOpPsfFUa7L88Lpy0= @@ -432,9 +494,14 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158 h1:CevA8fI91PAnP8vpnXuB8ZYAZ5wqY86nAbxfgK8tWO4= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd/v2 v2.0.1/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= @@ -722,11 +789,13 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.9/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021 h1:fP+fF0up6oPY49OrjPrhIJ8yQfdIM85NXMLkMg1EXVs= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1 h1:cgDRLG7bs59Zd+apAWuzLQL95obVYAymNJek76W3mgw= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.1 h1:4CF52PCseTFt4bE+Yk3dIpdVi7XWuPVMhPtm4FaIJPM= github.com/envoyproxy/protoc-gen-validate v0.6.1/go.mod h1:txg5va2Qkip90uYoSKH+nkAAmXrb2j3iq4FLwdrCbXQ= +github.com/envoyproxy/protoc-gen-validate v0.6.2 h1:JiO+kJTpmYGjEodY7O1Zk8oZcNz1+f30UtwtXoFUPzE= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/ericchiang/k8s v1.2.0/go.mod h1:/OmBgSq2cd9IANnsGHGlEz27nwMZV2YxlpXuQtU3Bz4= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= @@ -761,8 +830,9 @@ github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= @@ -783,6 +853,8 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NB github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/glinton/ping v0.1.4-0.20200311211934-5ac87da8cd96/go.mod h1:uY+1eqFUyotrQxF1wYFNtMeHp/swbYRsoGzfcPZ8x3o= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -865,7 +937,6 @@ github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3Hfo github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= @@ -907,7 +978,6 @@ github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeE github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ= -github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ= github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= @@ -951,8 +1021,13 @@ github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9G github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts= github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.0.0-beta.10.0.20200905143926-df7fe4e2ce72/go.mod h1:CJP1ZIHwhosNYwIdaHPZK9vHsM3+roNBaZ7U9Of1DXc= @@ -1042,8 +1117,9 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRsugc= github.com/gogo/status v1.1.0 h1:+eIkrewn5q6b30y+g/BJINVVdi2xH7je5MPJ3ZPK3JA= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= -github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0= +github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -1113,6 +1189,8 @@ github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cloud v0.24.1-0.20220209172924-99801bbb523a h1:Kw18HR30Firrm4cB1BzcVVaFrrdDlAtnrCKgHxemgMI= +github.com/google/go-cloud v0.24.1-0.20220209172924-99801bbb523a/go.mod h1:TqAL9Q5Q8jFUfIDNbDiFbU3w0z3MQ7q4r1wm57tBhmI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -1132,13 +1210,16 @@ github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3 github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= +github.com/google/go-replayers/httpreplay v1.0.0/go.mod h1:LJhKoTwS5Wy5Ld/peq8dFFG5OfJyHEz7ft+DsTUv25M= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= @@ -1158,12 +1239,12 @@ github.com/google/pprof v0.0.0-20201007051231-1066cbb265c7/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201117184057-ae444373da19/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210208152844-1612e9be7af6/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210323184331-8eee2492667d/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210504235042-3a04a4d88a10/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -1241,17 +1322,12 @@ github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f h1:FvvSVEbnGeM2bUivG github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f/go.mod h1:uPG2nyK4CtgNDmWv7qyzYcdI+S90kHHRWvHnBtEMBXM= github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA= github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/grafana/grafana-aws-sdk v0.9.1 h1:jMZlsLsWnqOwLt2UNcLUsJ2z6289hLYlscK35QgS158= -github.com/grafana/grafana-aws-sdk v0.9.1/go.mod h1:6KaQ8uUD4KpXr/b7bAC7zbfSXTVOiTk4XhIrwkGWn4w= -github.com/grafana/grafana-aws-sdk v0.10.0 h1:q7+mJtT/vsU5InDN57yM+BJ2z1kJDf1W4WwWPEZ0Cxw= -github.com/grafana/grafana-aws-sdk v0.10.0/go.mod h1:vFIOHEnY1u5nY0/tge1IHQjPuG6DRKr2ISf/HikUdjE= github.com/grafana/grafana-aws-sdk v0.10.1 h1:Ksguhjx6EuGLN/5Oc7oZoxuDReJ5RxIH99yqSMpLGUs= github.com/grafana/grafana-aws-sdk v0.10.1/go.mod h1:vFIOHEnY1u5nY0/tge1IHQjPuG6DRKr2ISf/HikUdjE= github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 h1:2ud7NNM7LrGPO4x0NFR8qLq68CqI4SmB7I2yRN2w9oE= github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE= github.com/grafana/grafana-plugin-sdk-go v0.94.0/go.mod h1:3VXz4nCv6wH5SfgB3mlW39s+c+LetqSCjFj7xxPC5+M= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= -github.com/grafana/grafana-plugin-sdk-go v0.125.0 h1:wK2zopAaKhVIMkXzgbExKqZtt+x2ZTGfcY+3wvOuyYQ= github.com/grafana/grafana-plugin-sdk-go v0.125.0/go.mod h1:9YiJ5GUxIsIEUC0qR9+BJVP5M7mCSP6uc6Ne62YKkgc= github.com/grafana/grafana-plugin-sdk-go v0.126.0 h1:GFstod7B/r5Ls9QiYV18fnOVtpWAtfR8aYSXfBvbCjE= github.com/grafana/grafana-plugin-sdk-go v0.126.0/go.mod h1:9YiJ5GUxIsIEUC0qR9+BJVP5M7mCSP6uc6Ne62YKkgc= @@ -1284,6 +1360,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.15.0/go.mod h1:vO11I9oWA+KsxmfFQPhLnnI github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= +github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= github.com/harlow/kinesis-consumer v0.3.1-0.20181230152818-2f58b136fee0/go.mod h1:dk23l2BruuUzRP8wbybQbPn3J7sZga2QHICCeaEy5rQ= github.com/hashicorp/consul v1.10.2 h1:9YX5SX3hMifrXIt9wqN2jJsMnESSHfxEjW5N7qMAdjo= github.com/hashicorp/consul v1.10.2/go.mod h1:EJMYpT39ZL2BnxjGRNTjfTH3s9893yd/DCX60PUnGUY= @@ -1424,6 +1502,7 @@ github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbc 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/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/igm/sockjs-go/v3 v3.0.1 h1:rmgEkeKqBHCFf7uIAipYrYSX8x9LBB2nOxAac2sooak= @@ -1570,13 +1649,15 @@ github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ= github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -1621,14 +1702,16 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/ragel-machinery v0.0.0-20181214104525-299bdde78165/go.mod h1:WZxr2/6a/Ar9bMDc2rN/LJrE/hF6bXE4LPyDSIxwAfg= github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.0/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= @@ -1643,10 +1726,10 @@ github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1 github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/iostat v1.1.0/go.mod h1:rEPNA0xXgjHQjuI5Cy05sLlS2oRcSlWHRLrvh/AQ+Pg= github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/m3db/prometheus_remote_client_golang v0.4.4 h1:DsAIjVKoCp7Ym35tAOFL1OuMLIdIikAEHeNPHY+yyM8= github.com/m3db/prometheus_remote_client_golang v0.4.4/go.mod h1:wHfVbA3eAK6dQvKjCkHhusWYegCk3bDGkA15zymSHdc= -github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls= github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.12.1 h1:oGdAbhIUd6iKamKlDGVtU6XGdy5SgNuCWn7gCTgHDtU= github.com/magefile/mage v1.12.1/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= @@ -1769,8 +1852,9 @@ github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1-0.20210112042008-8ebf2d61a8b4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -2485,6 +2569,7 @@ go.mongodb.org/mongo-driver v1.5.2/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS go.mongodb.org/mongo-driver v1.7.0 h1:hHrvOBWlWB2c7+8Gh/Xi5jj82AgidK/t7KVXBZ+IyUA= go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -2534,13 +2619,15 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.2.0/go.mod h1:YfO3fm683kQpzETxlTGZhGIVmXAhaw3gxeBADbpZtnU= go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -2550,6 +2637,7 @@ go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= @@ -2600,8 +2688,11 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2659,6 +2750,7 @@ golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hM golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2739,9 +2831,12 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211013171255-e13a2654a71e h1:Xj+JO91noE97IN6F/7WZxzC5QE6yENAQPrwIYhW3bsA= -golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= +golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2762,8 +2857,10 @@ golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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= @@ -2927,11 +3024,16 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2961,8 +3063,9 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -3064,7 +3167,6 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -3129,8 +3231,10 @@ google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.58.0 h1:MDkAbYIB1JpSgCTOCYYoIec/coMlKK4oVbpnBLLcyT0= google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.60.0 h1:eq/zs5WPH4J9undYM9IP1O7dSr7Yh8Y0GtSCpzGzIUk= +google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -3199,9 +3303,7 @@ google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210312152112-fc591d9ea70f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -3209,6 +3311,7 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -3227,8 +3330,14 @@ google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2 h1:CUp93KYgL06Y/PdI8aRJaFiAHevPIGWQmijSqaUhue8= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211019152133-63b7e35f4404/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -3268,8 +3377,9 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -3471,6 +3581,7 @@ modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6 modernc.org/memory v1.0.1/go.mod h1:NSjvC08+g3MLOpcAxQbdctcThAEX4YlJ20WWHYEhvRg= modernc.org/sqlite v1.7.4/go.mod h1:xse4RHCm8Fzw0COf5SJqAyiDrVeDwAQthAS1V/woNIA= modernc.org/tcl v1.4.1/go.mod h1:8YCvzidU9SIwkz7RZwlCWK61mhV8X9UwfkRDRp7y5e0= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index c566a4b2998..b838c5c379c 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -46,4 +46,5 @@ export interface FeatureToggles { dashboardComments?: boolean; annotationComments?: boolean; migrationLocking?: boolean; + fileStoreApi?: boolean; } diff --git a/pkg/infra/filestorage/api.go b/pkg/infra/filestorage/api.go new file mode 100644 index 00000000000..b89f42c46bb --- /dev/null +++ b/pkg/infra/filestorage/api.go @@ -0,0 +1,101 @@ +package filestorage + +import ( + "context" + "errors" + "strings" + "time" +) + +type StorageName string + +const ( + StorageNamePublic StorageName = "public" +) + +var ( + ErrRelativePath = errors.New("path cant be relative") + ErrNonCanonicalPath = errors.New("path must be canonical") + ErrPathTooLong = errors.New("path is too long") + ErrPathInvalid = errors.New("path is invalid") + ErrPathEndsWithDelimiter = errors.New("path can not end with delimiter") + Delimiter = "/" +) + +func Join(parts ...string) string { + return Delimiter + strings.Join(parts, Delimiter) +} + +func belongsToStorage(path string, storageName StorageName) bool { + return strings.HasPrefix(path, Delimiter+string(storageName)) +} + +type File struct { + Contents []byte + FileMetadata +} + +type FileMetadata struct { + Name string + FullPath string + MimeType string + Modified time.Time + Created time.Time + Size int64 + Properties map[string]string +} + +type ListFilesResponse struct { + Files []FileMetadata + HasMore bool + LastPath string +} + +type Paging struct { + After string + First int +} + +type UpsertFileCommand struct { + Path string + MimeType string + Contents *[]byte + Properties map[string]string +} + +type PathFilters struct { + allowedPrefixes []string +} + +func (f *PathFilters) isAllowed(path string) bool { + if f == nil || f.allowedPrefixes == nil { + return true + } + + for i := range f.allowedPrefixes { + if strings.HasPrefix(path, strings.ToLower(f.allowedPrefixes[i])) { + return true + } + } + + return false +} + +type ListOptions struct { + Recursive bool + PathFilters +} + +type FileStorage interface { + Get(ctx context.Context, path string) (*File, error) + Delete(ctx context.Context, path string) error + Upsert(ctx context.Context, command *UpsertFileCommand) error + + ListFiles(ctx context.Context, folderPath string, paging *Paging, options *ListOptions) (*ListFilesResponse, error) + ListFolders(ctx context.Context, folderPath string, options *ListOptions) ([]FileMetadata, error) + + CreateFolder(ctx context.Context, path string) error + DeleteFolder(ctx context.Context, path string) error + + close() error +} diff --git a/pkg/infra/filestorage/api_test.go b/pkg/infra/filestorage/api_test.go new file mode 100644 index 00000000000..a15d1f5edfe --- /dev/null +++ b/pkg/infra/filestorage/api_test.go @@ -0,0 +1,75 @@ +package filestorage + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilestorageApi_Join(t *testing.T) { + var tests = []struct { + name string + parts []string + expected string + }{ + { + name: "multiple parts", + parts: []string{"prefix", "p1", "p2"}, + expected: "/prefix/p1/p2", + }, + { + name: "no parts", + parts: []string{}, + expected: "/", + }, + { + name: "a single part", + parts: []string{"prefix"}, + expected: "/prefix", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expected, Join(tt.parts...)) + }) + } +} + +func TestFilestorageApi_belongToStorage(t *testing.T) { + var tests = []struct { + name string + path string + storage StorageName + expected bool + }{ + { + name: "should return true if path is prefixed with delimiter and the storage name", + path: "/public/abc/d", + storage: StorageNamePublic, + expected: true, + }, + { + name: "should return true if path consists just of the delimiter and the storage name", + path: "/public", + storage: StorageNamePublic, + expected: true, + }, + { + name: "should return false if path is not prefixed with delimiter", + path: "public/abc/d", + storage: StorageNamePublic, + expected: false, + }, + { + name: "should return false if storage name does not match", + path: "/notpublic/abc/d", + storage: StorageNamePublic, + expected: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expected, belongsToStorage(tt.path, tt.storage)) + }) + } +} diff --git a/pkg/infra/filestorage/cdk_blob_filestorage.go b/pkg/infra/filestorage/cdk_blob_filestorage.go new file mode 100644 index 00000000000..61691e7ea48 --- /dev/null +++ b/pkg/infra/filestorage/cdk_blob_filestorage.go @@ -0,0 +1,491 @@ +package filestorage + +import ( + "context" + "errors" + "fmt" + "io" + "strings" + + "github.com/grafana/grafana/pkg/infra/log" + "gocloud.dev/blob" + "gocloud.dev/gcerrors" +) + +const ( + originalPathAttributeKey = "__gf_original_path__" +) + +type cdkBlobStorage struct { + log log.Logger + bucket *blob.Bucket + rootFolder string +} + +func NewCdkBlobStorage(log log.Logger, bucket *blob.Bucket, rootFolder string, pathFilters *PathFilters) FileStorage { + return &wrapper{ + log: log, + wrapped: &cdkBlobStorage{ + log: log, + bucket: bucket, + rootFolder: rootFolder, + }, + pathFilters: pathFilters, + } +} + +func (c cdkBlobStorage) Get(ctx context.Context, filePath string) (*File, error) { + contents, err := c.bucket.ReadAll(ctx, strings.ToLower(filePath)) + if err != nil { + if gcerrors.Code(err) == gcerrors.NotFound { + return nil, nil + } + return nil, err + } + attributes, err := c.bucket.Attributes(ctx, strings.ToLower(filePath)) + if err != nil { + return nil, err + } + + var originalPath string + var props map[string]string + if attributes.Metadata != nil { + props = attributes.Metadata + if path, ok := attributes.Metadata[originalPathAttributeKey]; ok { + originalPath = path + delete(props, originalPathAttributeKey) + } + } else { + props = make(map[string]string) + originalPath = filePath + } + + return &File{ + Contents: contents, + FileMetadata: FileMetadata{ + Name: getName(originalPath), + FullPath: originalPath, + Created: attributes.CreateTime, + Properties: props, + Modified: attributes.ModTime, + Size: attributes.Size, + MimeType: detectContentType(originalPath, attributes.ContentType), + }, + }, nil +} + +func (c cdkBlobStorage) Delete(ctx context.Context, filePath string) error { + exists, err := c.bucket.Exists(ctx, strings.ToLower(filePath)) + if err != nil { + return err + } + + if !exists { + return nil + } + + err = c.bucket.Delete(ctx, strings.ToLower(filePath)) + return err +} + +func (c cdkBlobStorage) Upsert(ctx context.Context, command *UpsertFileCommand) error { + existing, err := c.Get(ctx, command.Path) + if err != nil { + return err + } + + var contents []byte + var metadata map[string]string + + if existing == nil { + if command.Contents == nil { + contents = make([]byte, 0) + } else { + contents = *command.Contents + } + + metadata = make(map[string]string) + if command.Properties != nil { + for k, v := range command.Properties { + metadata[k] = v + } + } + metadata[originalPathAttributeKey] = command.Path + return c.bucket.WriteAll(ctx, strings.ToLower(command.Path), contents, &blob.WriterOptions{ + Metadata: metadata, + }) + } + + contents = existing.Contents + if command.Contents != nil { + contents = *command.Contents + } + + if command.Properties != nil { + metadata = make(map[string]string) + for k, v := range command.Properties { + metadata[k] = v + } + } else { + metadata = existing.FileMetadata.Properties + } + + metadata[originalPathAttributeKey] = existing.FullPath + return c.bucket.WriteAll(ctx, strings.ToLower(command.Path), contents, &blob.WriterOptions{ + Metadata: metadata, + }) +} + +func (c cdkBlobStorage) listFiles(ctx context.Context, folderPath string, paging *Paging, options *ListOptions) (*ListFilesResponse, error) { + iterator := c.bucket.List(&blob.ListOptions{ + Prefix: strings.ToLower(folderPath), + Delimiter: Delimiter, + }) + + recursive := options.Recursive + + pageSize := paging.First + + foundCursor := true + if paging.After != "" { + foundCursor = false + } + + hasMore := true + files := make([]FileMetadata, 0) + for { + obj, err := iterator.Next(ctx) + if obj != nil && strings.HasSuffix(obj.Key, directoryMarker) { + continue + } + + if errors.Is(err, io.EOF) { + hasMore = false + break + } else { + hasMore = true + } + + if err != nil { + c.log.Error("Failed while iterating over files", "err", err) + return nil, err + } + + if len(files) >= pageSize { + break + } + + path := obj.Key + + allowed := options.isAllowed(obj.Key) + if obj.IsDir && recursive { + newPaging := &Paging{ + First: pageSize - len(files), + } + if paging != nil { + newPaging.After = paging.After + } + + resp, err := c.listFiles(ctx, path, newPaging, options) + + if err != nil { + return nil, err + } + + if len(files) > 0 { + foundCursor = true + } + + files = append(files, resp.Files...) + if len(files) >= pageSize { + //nolint: staticcheck + hasMore = resp.HasMore + } + } else if !obj.IsDir && allowed { + if !foundCursor { + res := strings.Compare(obj.Key, paging.After) + if res < 0 { + continue + } else if res == 0 { + foundCursor = true + continue + } else { + foundCursor = true + } + } + + attributes, err := c.bucket.Attributes(ctx, strings.ToLower(path)) + if err != nil { + c.log.Error("Failed while retrieving attributes", "path", path, "err", err) + return nil, err + } + + var originalPath string + var props map[string]string + if attributes.Metadata != nil { + props = attributes.Metadata + if path, ok := attributes.Metadata[originalPathAttributeKey]; ok { + originalPath = path + delete(props, originalPathAttributeKey) + } + } else { + props = make(map[string]string) + originalPath = fixPath(path) + } + + files = append(files, FileMetadata{ + Name: getName(originalPath), + FullPath: originalPath, + Created: attributes.CreateTime, + Properties: props, + Modified: attributes.ModTime, + Size: attributes.Size, + MimeType: detectContentType(originalPath, attributes.ContentType), + }) + } + } + + lastPath := "" + if len(files) > 0 { + lastPath = files[len(files)-1].FullPath + } + + return &ListFilesResponse{ + Files: files, + HasMore: hasMore, + LastPath: lastPath, + }, nil +} + +func (c cdkBlobStorage) fixInputPrefix(path string) string { + if path == Delimiter || path == "" { + return c.rootFolder + } + if strings.HasPrefix(path, Delimiter) { + path = fmt.Sprintf("%s%s", c.rootFolder, strings.TrimPrefix(path, Delimiter)) + } + + return path +} + +func (c cdkBlobStorage) convertFolderPathToPrefix(path string) string { + if path == Delimiter || path == "" { + return c.rootFolder + } + if strings.HasPrefix(path, Delimiter) { + path = fmt.Sprintf("%s%s", c.rootFolder, strings.TrimPrefix(path, Delimiter)) + } + return fmt.Sprintf("%s%s", path, Delimiter) +} + +func fixPath(path string) string { + newPath := strings.TrimSuffix(path, Delimiter) + if !strings.HasPrefix(newPath, Delimiter) { + newPath = fmt.Sprintf("%s%s", Delimiter, newPath) + } + return newPath +} + +func (c cdkBlobStorage) convertListOptions(options *ListOptions) *ListOptions { + if options == nil || options.allowedPrefixes == nil || len(options.allowedPrefixes) == 0 { + return options + } + + newPrefixes := make([]string, len(options.allowedPrefixes)) + for i, prefix := range options.allowedPrefixes { + newPrefixes[i] = c.fixInputPrefix(prefix) + } + + options.PathFilters.allowedPrefixes = newPrefixes + return options +} + +func (c cdkBlobStorage) ListFiles(ctx context.Context, folderPath string, paging *Paging, options *ListOptions) (*ListFilesResponse, error) { + paging.After = c.fixInputPrefix(paging.After) + return c.listFiles(ctx, c.convertFolderPathToPrefix(folderPath), paging, c.convertListOptions(options)) +} + +func (c cdkBlobStorage) listFolderPaths(ctx context.Context, parentFolderPath string, options *ListOptions) ([]string, error) { + iterator := c.bucket.List(&blob.ListOptions{ + Prefix: strings.ToLower(parentFolderPath), + Delimiter: Delimiter, + }) + + recursive := options.Recursive + + currentDirPath := "" + foundPaths := make([]string, 0) + for { + obj, err := iterator.Next(ctx) + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + c.log.Error("Failed while iterating over files", "err", err) + return nil, err + } + + if currentDirPath == "" && !obj.IsDir && options.isAllowed(obj.Key) { + attributes, err := c.bucket.Attributes(ctx, obj.Key) + if err != nil { + c.log.Error("Failed while retrieving attributes", "path", obj.Key, "err", err) + return nil, err + } + + if attributes.Metadata != nil { + if path, ok := attributes.Metadata[originalPathAttributeKey]; ok { + currentDirPath = getParentFolderPath(path) + } + } + } + + if obj.IsDir && recursive { + resp, err := c.listFolderPaths(ctx, obj.Key, options) + + if err != nil { + return nil, err + } + + if len(resp) > 0 { + foundPaths = append(foundPaths, resp...) + } + continue + } + } + + if currentDirPath != "" { + foundPaths = append(foundPaths, fixPath(currentDirPath)) + } + return foundPaths, nil +} + +func (c cdkBlobStorage) ListFolders(ctx context.Context, prefix string, options *ListOptions) ([]FileMetadata, error) { + foundPaths, err := c.listFolderPaths(ctx, c.convertFolderPathToPrefix(prefix), c.convertListOptions(options)) + if err != nil { + return nil, err + } + + folders := make([]FileMetadata, 0) + mem := make(map[string]bool) + for i := 0; i < len(foundPaths); i++ { + path := foundPaths[i] + parts := strings.Split(path, Delimiter) + acc := parts[0] + j := 1 + for { + acc = fmt.Sprintf("%s%s%s", acc, Delimiter, parts[j]) + + comparison := strings.Compare(acc, prefix) + if !mem[acc] && comparison > 0 { + folders = append(folders, FileMetadata{ + Name: getName(acc), + FullPath: acc, + }) + } + mem[acc] = true + + j += 1 + if j >= len(parts) { + break + } + } + } + + return folders, err +} + +func precedingFolders(path string) []string { + parts := strings.Split(path, Delimiter) + if len(parts) == 0 { + return []string{} + } + + if len(parts) == 1 { + return []string{path} + } + + currentDirPath := "" + firstPart := 0 + if parts[0] == "" { + firstPart = 1 + currentDirPath = Delimiter + } + + res := make([]string, 0) + for i := firstPart; i < len(parts); i++ { + res = append(res, currentDirPath+parts[i]) + currentDirPath += parts[i] + Delimiter + } + + return res +} + +func (c cdkBlobStorage) CreateFolder(ctx context.Context, path string) error { + c.log.Info("Creating folder", "path", path) + + precedingFolders := precedingFolders(path) + folderToOriginalCasing := make(map[string]string) + foundFolderIndex := -1 + + for i := len(precedingFolders) - 1; i >= 0; i-- { + currentFolder := precedingFolders[i] + att, err := c.bucket.Attributes(ctx, strings.ToLower(currentFolder+Delimiter+directoryMarker)) + if err != nil { + if gcerrors.Code(err) != gcerrors.NotFound { + return err + } + folderToOriginalCasing[currentFolder] = currentFolder + continue + } + + if path, ok := att.Metadata[originalPathAttributeKey]; ok { + folderToOriginalCasing[currentFolder] = getParentFolderPath(path) + foundFolderIndex = i + break + } else { + folderToOriginalCasing[currentFolder] = currentFolder + } + } + + for i := foundFolderIndex + 1; i < len(precedingFolders); i++ { + currentFolder := precedingFolders[i] + + previousFolderOriginalCasing := "" + if i > 0 { + previousFolderOriginalCasing = folderToOriginalCasing[precedingFolders[i-1]] + } + + metadata := make(map[string]string) + currentFolderWithOriginalCasing := previousFolderOriginalCasing + Delimiter + getName(currentFolder) + metadata[originalPathAttributeKey] = currentFolderWithOriginalCasing + Delimiter + directoryMarker + if err := c.bucket.WriteAll(ctx, strings.ToLower(metadata[originalPathAttributeKey]), make([]byte, 0), &blob.WriterOptions{ + Metadata: metadata, + }); err != nil { + return err + } + c.log.Info("Created folder", "path", currentFolderWithOriginalCasing, "marker", metadata[originalPathAttributeKey]) + } + + return nil +} + +func (c cdkBlobStorage) DeleteFolder(ctx context.Context, folderPath string) error { + directoryMarkerPath := fmt.Sprintf("%s%s%s", folderPath, Delimiter, directoryMarker) + exists, err := c.bucket.Exists(ctx, strings.ToLower(directoryMarkerPath)) + + if err != nil { + return err + } + + if !exists { + return nil + } + + err = c.bucket.Delete(ctx, strings.ToLower(directoryMarkerPath)) + return err +} + +func (c cdkBlobStorage) close() error { + return c.bucket.Close() +} diff --git a/pkg/infra/filestorage/db_filestorage.go b/pkg/infra/filestorage/db_filestorage.go new file mode 100644 index 00000000000..ff7ba4ecba2 --- /dev/null +++ b/pkg/infra/filestorage/db_filestorage.go @@ -0,0 +1,449 @@ +package filestorage + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/util/errutil" +) + +type file struct { + Path string `xorm:"path"` + ParentFolderPath string `xorm:"parent_folder_path"` + Contents []byte `xorm:"contents"` + Updated time.Time `xorm:"updated"` + Created time.Time `xorm:"created"` + Size int64 `xorm:"size"` + MimeType string `xorm:"mime_type"` +} + +type fileMeta struct { + Path string `xorm:"path"` + Key string `xorm:"key"` + Value string `xorm:"value"` +} + +type dbFileStorage struct { + db *sqlstore.SQLStore + log log.Logger +} + +func NewDbStorage(log log.Logger, db *sqlstore.SQLStore, pathFilters *PathFilters) FileStorage { + return &wrapper{ + log: log, + wrapped: &dbFileStorage{ + log: log, + db: db, + }, + pathFilters: pathFilters, + } +} + +func (s dbFileStorage) getProperties(sess *sqlstore.DBSession, lowerCasePaths []string) (map[string]map[string]string, error) { + attributesByPath := make(map[string]map[string]string) + + entities := make([]*fileMeta, 0) + if err := sess.Table("file_meta").In("path", lowerCasePaths).Find(&entities); err != nil { + return nil, err + } + + for _, entity := range entities { + if _, ok := attributesByPath[entity.Path]; !ok { + attributesByPath[entity.Path] = make(map[string]string) + } + attributesByPath[entity.Path][entity.Key] = entity.Value + } + + return attributesByPath, nil +} + +func (s dbFileStorage) Get(ctx context.Context, filePath string) (*File, error) { + var result *File + err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + table := &file{} + exists, err := sess.Table("file").Where("LOWER(path) = ?", strings.ToLower(filePath)).Get(table) + if !exists { + return nil + } + + var meta = make([]*fileMeta, 0) + if err := sess.Table("file_meta").Where("path = ?", strings.ToLower(filePath)).Find(&meta); err != nil { + return err + } + + var metaProperties = make(map[string]string, len(meta)) + + for i := range meta { + metaProperties[meta[i].Key] = meta[i].Value + } + + contents := table.Contents + if contents == nil { + contents = make([]byte, 0) + } + + result = &File{ + Contents: contents, + FileMetadata: FileMetadata{ + Name: getName(table.Path), + FullPath: table.Path, + Created: table.Created, + Properties: metaProperties, + Modified: table.Updated, + Size: table.Size, + MimeType: table.MimeType, + }, + } + return err + }) + + return result, err +} + +func (s dbFileStorage) Delete(ctx context.Context, filePath string) error { + err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + table := &file{} + exists, innerErr := sess.Table("file").Where("LOWER(path) = ?", strings.ToLower(filePath)).Get(table) + if innerErr != nil { + return innerErr + } + + if !exists { + return nil + } + + number, innerErr := sess.Table("file").Where("LOWER(path) = ?", strings.ToLower(filePath)).Delete(table) + if innerErr != nil { + return innerErr + } + s.log.Info("Deleted file", "path", filePath, "affectedRecords", number) + + metaTable := &fileMeta{} + number, innerErr = sess.Table("file_meta").Where("path = ?", strings.ToLower(filePath)).Delete(metaTable) + if innerErr != nil { + return innerErr + } + s.log.Info("Deleted metadata", "path", filePath, "affectedRecords", number) + return innerErr + }) + + return err +} + +func (s dbFileStorage) Upsert(ctx context.Context, cmd *UpsertFileCommand) error { + now := time.Now() + err := s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + existing := &file{} + exists, err := sess.Table("file").Where("LOWER(path) = ?", strings.ToLower(cmd.Path)).Get(existing) + if err != nil { + return err + } + + if exists { + existing.Updated = now + if cmd.Contents != nil { + contents := *cmd.Contents + existing.Contents = contents + existing.MimeType = cmd.MimeType + existing.Size = int64(len(contents)) + } + + _, err = sess.Where("LOWER(path) = ?", strings.ToLower(cmd.Path)).Update(existing) + if err != nil { + return err + } + } else { + contentsToInsert := make([]byte, 0) + if cmd.Contents != nil { + contentsToInsert = *cmd.Contents + } + + file := &file{ + Path: cmd.Path, + ParentFolderPath: getParentFolderPath(cmd.Path), + Contents: contentsToInsert, + MimeType: cmd.MimeType, + Size: int64(len(contentsToInsert)), + Updated: now, + Created: now, + } + _, err := sess.Insert(file) + if err != nil { + return err + } + } + + if len(cmd.Properties) != 0 { + if err = upsertProperties(sess, now, cmd); err != nil { + if rollbackErr := sess.Rollback(); rollbackErr != nil { + s.log.Error("failed while rolling back upsert", "path", cmd.Path) + } + return err + } + } + + return err + }) + + return err +} + +func upsertProperties(sess *sqlstore.DBSession, now time.Time, cmd *UpsertFileCommand) error { + fileMeta := &fileMeta{} + _, err := sess.Table("file_meta").Where("path = ?", strings.ToLower(cmd.Path)).Delete(fileMeta) + if err != nil { + return err + } + + for key, val := range cmd.Properties { + if err := upsertProperty(sess, now, cmd.Path, key, val); err != nil { + return err + } + } + return nil +} + +func upsertProperty(sess *sqlstore.DBSession, now time.Time, path string, key string, val string) error { + existing := &fileMeta{} + exists, err := sess.Table("file_meta").Where("path = ? AND key = ?", strings.ToLower(path), key).Get(existing) + if err != nil { + return err + } + + if exists { + existing.Value = val + _, err = sess.Where("path = ? AND key = ?", strings.ToLower(path), key).Update(existing) + } else { + _, err = sess.Insert(&fileMeta{ + Path: strings.ToLower(path), + Key: key, + Value: val, + }) + } + return err +} + +func (s dbFileStorage) ListFiles(ctx context.Context, folderPath string, paging *Paging, options *ListOptions) (*ListFilesResponse, error) { + var resp *ListFilesResponse + + err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + var foundFiles = make([]*file, 0) + + sess.Table("file") + lowerFolderPath := strings.ToLower(folderPath) + if options.Recursive { + var nestedFolders string + if folderPath == Delimiter { + nestedFolders = "%" + } else { + nestedFolders = fmt.Sprintf("%s%s%s", lowerFolderPath, Delimiter, "%") + } + sess.Where("(LOWER(parent_folder_path) = ?) OR (LOWER(parent_folder_path) LIKE ?)", lowerFolderPath, nestedFolders) + } else { + sess.Where("LOWER(parent_folder_path) = ?", lowerFolderPath) + } + sess.Where("LOWER(path) NOT LIKE ?", fmt.Sprintf("%s%s%s", "%", Delimiter, directoryMarker)) + + for _, prefix := range options.PathFilters.allowedPrefixes { + sess.Where("LOWER(path) LIKE ?", fmt.Sprintf("%s%s", strings.ToLower(prefix), "%")) + } + + sess.OrderBy("path") + + pageSize := paging.First + sess.Limit(pageSize + 1) + + if paging != nil && paging.After != "" { + sess.Where("path > ?", paging.After) + } + + if err := sess.Find(&foundFiles); err != nil { + return err + } + + foundLength := len(foundFiles) + if foundLength > pageSize { + foundLength = pageSize + } + + lowerCasePaths := make([]string, 0) + for i := 0; i < foundLength; i++ { + lowerCasePaths = append(lowerCasePaths, strings.ToLower(foundFiles[i].Path)) + } + propertiesByLowerPath, err := s.getProperties(sess, lowerCasePaths) + if err != nil { + return err + } + + files := make([]FileMetadata, 0) + for i := 0; i < foundLength; i++ { + var props map[string]string + path := foundFiles[i].Path + if foundProps, ok := propertiesByLowerPath[strings.ToLower(path)]; ok { + props = foundProps + } else { + props = make(map[string]string) + } + + files = append(files, FileMetadata{ + Name: getName(path), + FullPath: path, + Created: foundFiles[i].Created, + Properties: props, + Modified: foundFiles[i].Updated, + Size: foundFiles[i].Size, + MimeType: foundFiles[i].MimeType, + }) + } + + lastPath := "" + if len(files) > 0 { + lastPath = files[len(files)-1].FullPath + } + + resp = &ListFilesResponse{ + Files: files, + LastPath: lastPath, + HasMore: len(foundFiles) == pageSize+1, + } + return nil + }) + + return resp, err +} + +func (s dbFileStorage) ListFolders(ctx context.Context, parentFolderPath string, options *ListOptions) ([]FileMetadata, error) { + folders := make([]FileMetadata, 0) + err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + var foundPaths []string + + sess.Table("file") + sess.Distinct("parent_folder_path") + + if options.Recursive { + sess.Where("LOWER(parent_folder_path) > ?", strings.ToLower(parentFolderPath)) + } else { + sess.Where("LOWER(parent_folder_path) = ?", strings.ToLower(parentFolderPath)) + } + + for _, prefix := range options.PathFilters.allowedPrefixes { + sess.Where("LOWER(parent_folder_path) LIKE ?", fmt.Sprintf("%s%s", strings.ToLower(prefix), "%")) + } + + sess.OrderBy("parent_folder_path") + sess.Cols("parent_folder_path") + + if err := sess.Find(&foundPaths); err != nil { + return err + } + + mem := make(map[string]bool) + for i := 0; i < len(foundPaths); i++ { + path := foundPaths[i] + parts := strings.Split(path, Delimiter) + acc := parts[0] + j := 1 + for { + acc = fmt.Sprintf("%s%s%s", acc, Delimiter, parts[j]) + comparison := strings.Compare(acc, parentFolderPath) + if !mem[acc] && comparison > 0 { + folders = append(folders, FileMetadata{ + Name: getName(acc), + FullPath: acc, + }) + } + mem[acc] = true + + j += 1 + if j >= len(parts) { + break + } + } + } + + return nil + }) + + return folders, err +} + +func (s dbFileStorage) CreateFolder(ctx context.Context, path string) error { + now := time.Now() + precedingFolders := precedingFolders(path) + + err := s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + var insertErr error + sess.MustLogSQL(true) + previousFolder := "" + for i := 0; i < len(precedingFolders); i++ { + existing := &file{} + directoryMarkerParentPath := previousFolder + Delimiter + getName(precedingFolders[i]) + previousFolder = directoryMarkerParentPath + directoryMarkerPath := fmt.Sprintf("%s%s%s", directoryMarkerParentPath, Delimiter, directoryMarker) + lower := strings.ToLower(directoryMarkerPath) + exists, err := sess.Table("file").Where("LOWER(path) = ?", lower).Get(existing) + if err != nil { + insertErr = err + break + } + + if exists { + previousFolder = existing.ParentFolderPath + continue + } + + file := &file{ + Path: strings.ToLower(directoryMarkerPath), + ParentFolderPath: directoryMarkerParentPath, + Contents: make([]byte, 0), + Updated: now, + Created: now, + } + _, err = sess.Insert(file) + if err != nil { + insertErr = err + break + } + s.log.Info("Created folder", "markerPath", file.Path, "parent", file.ParentFolderPath) + } + + if insertErr != nil { + if rollErr := sess.Rollback(); rollErr != nil { + return errutil.Wrapf(insertErr, "Rolling back transaction due to error failed: %s", rollErr) + } + return insertErr + } + + return sess.Commit() + }) + + return err +} + +func (s dbFileStorage) DeleteFolder(ctx context.Context, folderPath string) error { + err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + existing := &file{} + directoryMarkerPath := fmt.Sprintf("%s%s%s", folderPath, Delimiter, directoryMarker) + exists, err := sess.Table("file").Where("LOWER(path) = ?", strings.ToLower(directoryMarkerPath)).Get(existing) + if err != nil { + return err + } + + if !exists { + return nil + } + + _, err = sess.Table("file").Where("LOWER(path) = ?", strings.ToLower(directoryMarkerPath)).Delete(existing) + return err + }) + + return err +} + +func (s dbFileStorage) close() error { + return nil +} diff --git a/pkg/infra/filestorage/dummy.go b/pkg/infra/filestorage/dummy.go new file mode 100644 index 00000000000..73db7a92bfe --- /dev/null +++ b/pkg/infra/filestorage/dummy.go @@ -0,0 +1,51 @@ +package filestorage + +import ( + "context" + + _ "gocloud.dev/blob/fileblob" + _ "gocloud.dev/blob/memblob" +) + +var ( + _ FileStorage = (*dummyFileStorage)(nil) // dummyFileStorage implements FileStorage +) + +type dummyFileStorage struct { +} + +func (d dummyFileStorage) Get(ctx context.Context, path string) (*File, error) { + return nil, nil +} + +func (d dummyFileStorage) Delete(ctx context.Context, path string) error { + return nil +} + +func (d dummyFileStorage) Upsert(ctx context.Context, file *UpsertFileCommand) error { + return nil +} + +func (d dummyFileStorage) ListFiles(ctx context.Context, path string, cursor *Paging, options *ListOptions) (*ListFilesResponse, error) { + return nil, nil +} + +func (d dummyFileStorage) ListFolders(ctx context.Context, path string, options *ListOptions) ([]FileMetadata, error) { + return nil, nil +} + +func (d dummyFileStorage) CreateFolder(ctx context.Context, path string) error { + return nil +} + +func (d dummyFileStorage) DeleteFolder(ctx context.Context, path string) error { + return nil +} + +func (d dummyFileStorage) IsFolderEmpty(ctx context.Context, path string) (bool, error) { + return true, nil +} + +func (d dummyFileStorage) close() error { + return nil +} diff --git a/pkg/infra/filestorage/filestorage.go b/pkg/infra/filestorage/filestorage.go new file mode 100644 index 00000000000..5c9e1ae952f --- /dev/null +++ b/pkg/infra/filestorage/filestorage.go @@ -0,0 +1,160 @@ +package filestorage + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" + "gocloud.dev/blob" + + _ "gocloud.dev/blob/fileblob" + _ "gocloud.dev/blob/memblob" +) + +const ( + ServiceName = "FileStorage" +) + +func ProvideService(features featuremgmt.FeatureToggles, cfg *setting.Cfg) (FileStorage, error) { + grafanaDsStorageLogger := log.New("grafanaDsStorage") + + path := fmt.Sprintf("file://%s", cfg.StaticRootPath) + grafanaDsStorageLogger.Info("Initializing grafana ds storage", "path", path) + bucket, err := blob.OpenBucket(context.Background(), path) + if err != nil { + currentDir, _ := os.Getwd() + grafanaDsStorageLogger.Error("Failed to initialize grafana ds storage", "path", path, "error", err, "cwd", currentDir) + return nil, err + } + + prefixes := []string{ + "testdata/", + "img/icons/", + "img/bg/", + "gazetteer/", + "maps/", + "upload/", + } + + var grafanaDsStorage FileStorage + if features.IsEnabled(featuremgmt.FlagFileStoreApi) { + grafanaDsStorage = &wrapper{ + log: grafanaDsStorageLogger, + wrapped: cdkBlobStorage{ + log: grafanaDsStorageLogger, + bucket: bucket, + rootFolder: "", + }, + pathFilters: &PathFilters{allowedPrefixes: prefixes}, + } + } else { + grafanaDsStorage = &dummyFileStorage{} + } + + return &service{ + grafanaDsStorage: grafanaDsStorage, + log: log.New("fileStorageService"), + }, nil +} + +type service struct { + log log.Logger + grafanaDsStorage FileStorage +} + +func (b service) Get(ctx context.Context, path string) (*File, error) { + var filestorage FileStorage + if belongsToStorage(path, StorageNamePublic) { + filestorage = b.grafanaDsStorage + path = removeStoragePrefix(path) + } + + if err := validatePath(path); err != nil { + return nil, err + } + + return filestorage.Get(ctx, path) +} + +func removeStoragePrefix(path string) string { + path = strings.TrimPrefix(path, Delimiter) + if path == Delimiter || path == "" { + return Delimiter + } + + if !strings.Contains(path, Delimiter) { + return Delimiter + } + + split := strings.Split(path, Delimiter) + + // root of storage + if len(split) == 2 && split[1] == "" { + return Delimiter + } + + // replace storage + split[0] = "" + return strings.Join(split, Delimiter) +} + +func (b service) Delete(ctx context.Context, path string) error { + return errors.New("not implemented") +} + +func (b service) Upsert(ctx context.Context, file *UpsertFileCommand) error { + return errors.New("not implemented") +} + +func (b service) ListFiles(ctx context.Context, path string, cursor *Paging, options *ListOptions) (*ListFilesResponse, error) { + var filestorage FileStorage + if belongsToStorage(path, StorageNamePublic) { + filestorage = b.grafanaDsStorage + path = removeStoragePrefix(path) + } else { + return nil, errors.New("not implemented") + } + + if err := validatePath(path); err != nil { + return nil, err + } + + return filestorage.ListFiles(ctx, path, cursor, options) +} + +func (b service) ListFolders(ctx context.Context, path string, options *ListOptions) ([]FileMetadata, error) { + var filestorage FileStorage + if belongsToStorage(path, StorageNamePublic) { + filestorage = b.grafanaDsStorage + path = removeStoragePrefix(path) + } else { + return nil, errors.New("not implemented") + } + + if err := validatePath(path); err != nil { + return nil, err + } + + return filestorage.ListFolders(ctx, path, options) +} + +func (b service) CreateFolder(ctx context.Context, path string) error { + return errors.New("not implemented") +} + +func (b service) DeleteFolder(ctx context.Context, path string) error { + return errors.New("not implemented") +} + +func (b service) IsFolderEmpty(ctx context.Context, path string) (bool, error) { + return true, errors.New("not implemented") +} + +func (b service) close() error { + return b.grafanaDsStorage.close() +} diff --git a/pkg/infra/filestorage/filestorage_test.go b/pkg/infra/filestorage/filestorage_test.go new file mode 100644 index 00000000000..f432c849195 --- /dev/null +++ b/pkg/infra/filestorage/filestorage_test.go @@ -0,0 +1,46 @@ +package filestorage + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilestorage_removeStoragePrefix(t *testing.T) { + var tests = []struct { + name string + path string + expected string + }{ + { + name: "should return root if path is empty", + path: "", + expected: Delimiter, + }, + { + name: "should remove prefix folder from path with multiple parts", + path: "public/abc/d", + expected: "/abc/d", + }, + { + name: "should return root path if path is just the storage name", + path: "public", + expected: Delimiter, + }, + { + name: "should return root path if path is the prefix of storage", + path: "public/", + expected: Delimiter, + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%s%s", "absolute: ", tt.name), func(t *testing.T) { + require.Equal(t, tt.expected, removeStoragePrefix(Delimiter+tt.path)) + }) + + t.Run(fmt.Sprintf("%s%s", "relative: ", tt.name), func(t *testing.T) { + require.Equal(t, tt.expected, removeStoragePrefix(tt.path)) + }) + } +} diff --git a/pkg/infra/filestorage/fs_integration_test.go b/pkg/infra/filestorage/fs_integration_test.go new file mode 100644 index 00000000000..2440ad34463 --- /dev/null +++ b/pkg/infra/filestorage/fs_integration_test.go @@ -0,0 +1,857 @@ +//go:build integration +// +build integration + +package filestorage + +import ( + "context" + "encoding/base64" + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/grafana/grafana/pkg/infra/log" + "gocloud.dev/blob" +) + +const ( + pngImageBase64 = "iVBORw0KGgoNAANSUhEUgAAAC4AAAAmCAYAAAC76qlaAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAABFSURBVFiF7c5BDQAhEACx4/x7XjzwGELSKuiamfke9N8OnBKvidfEa+I18Zp4TbwmXhOvidfEa+I18Zp4TbwmXhOvidc2lcsESD1LGnUAAAAASUVORK5CYII=" +) + +type fsTestCase struct { + name string + skip *bool + steps []interface{} +} + +func runTestCase(t *testing.T, testCase fsTestCase, ctx context.Context, filestorage FileStorage) { + if testCase.skip != nil { + return + } + for i, step := range testCase.steps { + executeTestStep(t, ctx, step, i, filestorage) + } +} + +func runTests(createCases func() []fsTestCase, t *testing.T) { + var testLogger log.Logger + //var sqlStore *sqlstore.SQLStore + var filestorage FileStorage + var ctx context.Context + var tempDir string + + commonSetup := func() { + testLogger = log.New("testStorageLogger") + ctx = context.Background() + } + + cleanUp := func() { + testLogger = nil + //sqlStore = nil + if filestorage != nil { + _ = filestorage.close() + filestorage = nil + } + + ctx = nil + _ = os.RemoveAll(tempDir) + } + + setupInMemFS := func() { + commonSetup() + bucket, _ := blob.OpenBucket(context.Background(), "mem://") + filestorage = NewCdkBlobStorage(testLogger, bucket, Delimiter, nil) + } + + //setupSqlFS := func() { + // commonSetup() + // sqlStore = sqlstore.InitTestDB(t) + // filestorage = NewDbStorage(testLogger, sqlStore, nil) + //} + + setupLocalFs := func() { + commonSetup() + tmpDir, err := ioutil.TempDir("", "") + tempDir = tmpDir + if err != nil { + t.Fatal(err) + } + + bucket, err := blob.OpenBucket(context.Background(), fmt.Sprintf("file://%s", tmpDir)) + if err != nil { + t.Fatal(err) + } + filestorage = NewCdkBlobStorage(testLogger, bucket, "", nil) + } + + backends := []struct { + setup func() + name string + }{ + { + setup: setupLocalFs, + name: "Local FS", + }, + { + setup: setupInMemFS, + name: "In-mem FS", + }, + //{ + // setup: setupSqlFS, + // name: "SQL FS", + //}, + } + + for _, backend := range backends { + for _, tt := range createCases() { + t.Run(fmt.Sprintf("%s: %s", backend.name, tt.name), func(t *testing.T) { + backend.setup() + defer cleanUp() + runTestCase(t, tt, ctx, filestorage) + }) + } + } +} + +func TestFsStorage(t *testing.T) { + //skipTest := true + emptyFileBytes := make([]byte, 0) + pngImage, _ := base64.StdEncoding.DecodeString(pngImageBase64) + pngImageSize := int64(len(pngImage)) + + createListFilesTests := func() []fsTestCase { + return []fsTestCase{ + { + name: "listing files", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/folder2/file.jpg", + Contents: &[]byte{}, + Properties: map[string]string{"prop1": "val1", "prop2": "val"}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/file-inner.jpg", + Contents: &[]byte{}, + Properties: map[string]string{"prop1": "val1", "prop2": "val"}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/file-inner2.jpg", + Contents: &[]byte{}, + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true}}, + list: checks(listSize(3), listHasMore(false), listLastPath("/folder1/folder2/file.jpg")), + files: [][]interface{}{ + checks(fPath("/folder1/file-inner.jpg"), fProperties(map[string]string{"prop1": "val1", "prop2": "val"})), + checks(fPath("/folder1/file-inner2.jpg"), fProperties(map[string]string{})), + checks(fPath("/folder1/folder2/file.jpg"), fProperties(map[string]string{"prop1": "val1", "prop2": "val"})), + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: false}}, + list: checks(listSize(0), listHasMore(false), listLastPath("")), + files: [][]interface{}{}, + }, + queryListFiles{ + input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: false}}, + list: checks(listSize(2), listHasMore(false), listLastPath("/folder1/file-inner2.jpg")), + files: [][]interface{}{ + checks(fPath("/folder1/file-inner.jpg"), fProperties(map[string]string{"prop1": "val1", "prop2": "val"})), + checks(fPath("/folder1/file-inner2.jpg"), fProperties(map[string]string{})), + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/folder1/folder2", options: &ListOptions{Recursive: false}}, + list: checks(listSize(1), listHasMore(false), listLastPath("/folder1/folder2/file.jpg")), + files: [][]interface{}{ + checks(fPath("/folder1/folder2/file.jpg"), fProperties(map[string]string{"prop1": "val1", "prop2": "val"})), + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/folder1/folder2", options: &ListOptions{Recursive: false}, paging: &Paging{After: "/folder1/folder2/file.jpg"}}, + list: checks(listSize(0), listHasMore(false), listLastPath("")), + files: [][]interface{}{}, + }, + }, + }, + { + name: "path passed to listing files is a folder path, not a prefix", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/ab/a.jpg", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/ab/a/a.jpg", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/ac/a.jpg", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/aba/a.jpg", + Contents: &[]byte{}, + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/ab", options: &ListOptions{Recursive: true}}, + list: checks(listSize(2), listHasMore(false), listLastPath("/ab/a/a.jpg")), + files: [][]interface{}{ + checks(fPath("/ab/a.jpg")), + checks(fPath("/ab/a/a.jpg")), + }, + }, + }, + }, + { + name: "listing files with prefix filter", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/folder2/file.jpg", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/file-inner.jpg", + Contents: &[]byte{}, + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, PathFilters: PathFilters{allowedPrefixes: []string{"/folder2"}}}}, + list: checks(listSize(0), listHasMore(false), listLastPath("")), + }, + queryListFiles{ + input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, PathFilters: PathFilters{allowedPrefixes: []string{"/folder1/folder"}}}}, + list: checks(listSize(1), listHasMore(false)), + files: [][]interface{}{ + checks(fPath("/folder1/folder2/file.jpg")), + }, + }, + }, + }, + { + name: "listing files with pagination", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/a", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/b", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder2/c", + Contents: &[]byte{}, + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 1, After: ""}}, + list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/a")), + files: [][]interface{}{ + checks(fPath("/folder1/a")), + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 1, After: "/folder1/a"}}, + list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/b")), + files: [][]interface{}{ + checks(fPath("/folder1/b")), + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 1, After: "/folder1/b"}}, + list: checks(listSize(1), listHasMore(false), listLastPath("/folder2/c")), + files: [][]interface{}{ + checks(fPath("/folder2/c")), + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: ""}}, + list: checks(listSize(3), listHasMore(false), listLastPath("/folder2/c")), + files: [][]interface{}{ + checks(fPath("/folder1/a")), + checks(fPath("/folder1/b")), + checks(fPath("/folder2/c")), + }, + }, + queryListFiles{ + input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: "/folder2"}}, + list: checks(listSize(1), listHasMore(false)), + }, + queryListFiles{ + input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: "/folder2/c"}}, + list: checks(listSize(0), listHasMore(false)), + }, + }, + }, + } + } + + createListFoldersTests := func() []fsTestCase { + return []fsTestCase{ + { + name: "listing folders", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/folder2/file.jpg", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder1/file-inner.jpg", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folderX/folderZ/file.txt", + Contents: &[]byte{}, + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folderA/folderB/file.txt", + Contents: &[]byte{}, + }, + }, + queryListFolders{ + input: queryListFoldersInput{path: "/", options: &ListOptions{Recursive: true}}, + checks: [][]interface{}{ + checks(fPath("/folder1")), + checks(fPath("/folder1/folder2")), + checks(fPath("/folderA")), + checks(fPath("/folderA/folderB")), + checks(fPath("/folderX")), + checks(fPath("/folderX/folderZ")), + }, + }, + }, + }, + } + } + + createFileCRUDTests := func() []fsTestCase { + return []fsTestCase{ + { + name: "getting a non-existent file", + steps: []interface{}{ + queryGet{ + input: queryGetInput{ + path: "/folder/a.png", + }, + }, + }, + }, + { + name: "inserting a file", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder/a.png", + Contents: &pngImage, + Properties: map[string]string{"prop1": "val1", "prop2": "val"}, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/folder/a.png", + }, + checks: checks( + fPath("/folder/a.png"), + fName("a.png"), + fMimeType("image/png"), + fProperties(map[string]string{"prop1": "val1", "prop2": "val"}), + fSize(pngImageSize), + fContents(pngImage), + ), + }, + }, + }, + { + name: "preserved original path/name casing when getting a file", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/Folder/A.png", + Contents: &emptyFileBytes, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/fOlder/a.png", + }, + checks: checks( + fPath("/Folder/A.png"), + fName("A.png"), + ), + }, + }, + }, + { + name: "modifying file metadata", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/a.png", + Contents: &pngImage, + Properties: map[string]string{"a": "av", "b": "bv"}, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/a.png", + }, + checks: checks( + fContents(pngImage), + fProperties(map[string]string{"a": "av", "b": "bv"}), + ), + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/a.png", + Properties: map[string]string{"b": "bv2", "c": "cv"}, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/a.png", + }, + checks: checks( + fContents(pngImage), + fProperties(map[string]string{"b": "bv2", "c": "cv"}), + ), + }, + }, + }, + { + name: "modifying file metadata preserves original path casing", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/aB.png", + Contents: &emptyFileBytes, + Properties: map[string]string{"a": "av", "b": "bv"}, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/ab.png", + }, + checks: checks( + fPath("/aB.png"), + fName("aB.png"), + ), + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/ab.png", + Properties: map[string]string{"b": "bv2", "c": "cv"}, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/ab.png", + }, + checks: checks( + fPath("/aB.png"), + fName("aB.png"), + fProperties(map[string]string{"b": "bv2", "c": "cv"}), + ), + }, + }, + }, + { + name: "modifying file contents", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/FILE.png", + Contents: &emptyFileBytes, + Properties: map[string]string{"a": "av", "b": "bv"}, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/file.png", + }, + checks: checks( + fName("FILE.png"), + fProperties(map[string]string{"a": "av", "b": "bv"}), + fSize(0), + fContents(emptyFileBytes), + ), + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/file.png", + Contents: &pngImage, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/file.png", + }, + checks: checks( + fName("FILE.png"), + fMimeType("image/png"), + fProperties(map[string]string{"a": "av", "b": "bv"}), + fSize(pngImageSize), + fContents(pngImage), + ), + }, + }, + }, + { + name: "deleting a file", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/FILE.png", + Contents: &emptyFileBytes, + Properties: map[string]string{"a": "av", "b": "bv"}, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/file.png", + }, + checks: checks( + fPath("/FILE.png"), + ), + }, + cmdDelete{ + path: "/file.png", + }, + queryGet{ + input: queryGetInput{ + path: "/file.png", + }, + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/file.png", + Contents: &emptyFileBytes, + Properties: map[string]string{"a": "av", "b": "bv"}, + }, + }, + queryGet{ + input: queryGetInput{ + path: "/file.png", + }, + checks: checks( + fPath("/file.png"), + ), + }, + }, + }, + { + name: "deleting a non-existent file should be no-op", + steps: []interface{}{ + cmdDelete{ + path: "/file.png", + }, + }, + }, + } + } + + createFolderCrudCases := func() []fsTestCase { + return []fsTestCase{ + { + name: "recreating a folder after it was already created via upserting a file is a no-op", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/aB/cD/eF/file.jpg", + Contents: &[]byte{}, + }, + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/aB")), + checks(fPath("/aB/cD")), + checks(fPath("/aB/cD/eF")), + }, + }, + cmdCreateFolder{ + path: "/ab/cd/ef", + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/aB")), + checks(fPath("/aB/cD")), + checks(fPath("/aB/cD/eF")), + }, + }, + cmdCreateFolder{ + path: "/ab/cd/ef/GH", + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/aB")), + checks(fPath("/aB/cD")), + checks(fPath("/aB/cD/eF")), + checks(fPath("/aB/cD/eF/GH")), + }, + }, + }, + }, + { + name: "creating a folder with the same name or same name but different casing is a no-op", + steps: []interface{}{ + cmdCreateFolder{ + path: "/aB", + }, + cmdCreateFolder{ + path: "/ab", + }, + cmdCreateFolder{ + path: "/aB", + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/aB")), + }, + }, + cmdCreateFolder{ + path: "/Ab", + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/aB")), + }, + }, + }, + }, + { + name: "creating folder is recursive", + steps: []interface{}{ + cmdCreateFolder{ + path: "/a/b/c", + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/a")), + checks(fPath("/a/b")), + checks(fPath("/a/b/c")), + }, + }, + }, + }, + { + name: "deleting a leaf directory does not delete parent directories even if they are empty - folders created directly", + steps: []interface{}{ + cmdCreateFolder{ + path: "/a/b/c", + }, + cmdDeleteFolder{ + path: "/a/b/c", + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/a")), + checks(fPath("/a/b")), + }, + }, + }, + }, + { + name: "deleting a leaf directory does not delete parent directories even if they are empty - folders created via file upsert", + steps: []interface{}{ + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/a/b/c/file.jpg", + Contents: &[]byte{}, + }, + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/a")), + checks(fPath("/a/b")), + checks(fPath("/a/b/c")), + }, + }, + cmdDelete{ + path: "/a/b/c/file.jpg", + error: nil, + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/a")), + checks(fPath("/a/b")), + checks(fPath("/a/b/c")), + }, + }, + cmdDeleteFolder{ + path: "/a/b/c", + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/a")), + checks(fPath("/a/b")), + }, + }, + }, + }, + { + name: "folders preserve their original casing", + steps: []interface{}{ + cmdCreateFolder{ + path: "/aB/cD/e", + }, + cmdCreateFolder{ + path: "/ab/cd/f", + }, + queryListFolders{ + input: queryListFoldersInput{ + path: "/", + }, + checks: [][]interface{}{ + checks(fPath("/aB")), + checks(fPath("/aB/cD")), + checks(fPath("/aB/cD/e")), + checks(fPath("/aB/cD/f")), + }, + }, + }, + }, + { + name: "folders can't be deleted through the `delete` method", + steps: []interface{}{ + cmdCreateFolder{ + path: "/folder/dashboards/myNewFolder", + }, + queryListFolders{ + input: queryListFoldersInput{path: "/", options: &ListOptions{Recursive: true}}, + checks: [][]interface{}{ + checks(fPath("/folder")), + checks(fPath("/folder/dashboards")), + checks(fPath("/folder/dashboards/myNewFolder")), + }, + }, + cmdDelete{ + path: "/folder/dashboards/myNewFolder", + }, + queryListFolders{ + input: queryListFoldersInput{path: "/", options: &ListOptions{Recursive: true}}, + checks: [][]interface{}{ + checks(fPath("/folder")), + checks(fPath("/folder/dashboards")), + checks(fPath("/folder/dashboards/myNewFolder")), + }, + }, + }, + }, + { + name: "folders can not be retrieved through the `get` method", + steps: []interface{}{ + cmdCreateFolder{ + path: "/folder/dashboards/myNewFolder", + }, + queryGet{ + input: queryGetInput{ + path: "/folder/dashboards/myNewFolder", + }, + }, + }, + }, + { + name: "should not be able to delete folders with files", + steps: []interface{}{ + cmdCreateFolder{ + path: "/folder/dashboards/myNewFolder", + }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/folder/dashboards/myNewFolder/file.jpg", + Contents: &[]byte{}, + }, + }, + cmdDeleteFolder{ + path: "/folder/dashboards/myNewFolder", + error: &cmdErrorOutput{ + message: "folder %s is not empty - cant remove it", + args: []interface{}{"/folder/dashboards/myNewFolder"}, + }, + }, + queryListFolders{ + input: queryListFoldersInput{path: "/", options: &ListOptions{Recursive: true}}, + checks: [][]interface{}{ + checks(fPath("/folder")), + checks(fPath("/folder/dashboards")), + checks(fPath("/folder/dashboards/myNewFolder")), + }, + }, + queryGet{ + input: queryGetInput{ + path: "/folder/dashboards/myNewFolder/file.jpg", + }, + checks: checks( + fName("file.jpg"), + ), + }, + }, + }, + } + } + + runTests(createListFoldersTests, t) + runTests(createListFilesTests, t) + runTests(createFileCRUDTests, t) + runTests(createFolderCrudCases, t) +} diff --git a/pkg/infra/filestorage/test_utils.go b/pkg/infra/filestorage/test_utils.go new file mode 100644 index 00000000000..2ecbb1054aa --- /dev/null +++ b/pkg/infra/filestorage/test_utils.go @@ -0,0 +1,346 @@ +//go:build integration +// +build integration + +package filestorage + +import ( + "context" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +type cmdErrorOutput struct { + message string + args []interface{} + instance error +} + +type cmdDelete struct { + path string + error *cmdErrorOutput +} + +type cmdUpsert struct { + cmd UpsertFileCommand + error *cmdErrorOutput +} + +type cmdCreateFolder struct { + path string + error *cmdErrorOutput +} + +type cmdDeleteFolder struct { + path string + error *cmdErrorOutput +} + +type queryGetInput struct { + path string +} + +type fileNameCheck struct { + v string +} + +type filePropertiesCheck struct { + v map[string]string +} + +type fileContentsCheck struct { + v []byte +} + +type fileSizeCheck struct { + v int64 +} + +type fileMimeTypeCheck struct { + v string +} + +type filePathCheck struct { + v string +} + +type listSizeCheck struct { + v int +} + +type listHasMoreCheck struct { + v bool +} + +type listLastPathCheck struct { + v string +} + +func fContents(contents []byte) interface{} { + return fileContentsCheck{v: contents} +} + +func fName(name string) interface{} { + return fileNameCheck{v: name} +} + +func fPath(path string) interface{} { + return filePathCheck{v: path} +} + +func fProperties(properties map[string]string) interface{} { + return filePropertiesCheck{v: properties} +} +func fSize(size int64) interface{} { + return fileSizeCheck{v: size} +} + +func fMimeType(mimeType string) interface{} { + return fileMimeTypeCheck{v: mimeType} +} + +func listSize(size int) interface{} { + return listSizeCheck{v: size} +} + +func listHasMore(hasMore bool) interface{} { + return listHasMoreCheck{v: hasMore} +} + +func listLastPath(path string) interface{} { + return listLastPathCheck{v: path} +} + +func checks(c ...interface{}) []interface{} { + return c +} + +type queryGet struct { + input queryGetInput + checks []interface{} +} + +type queryListFilesInput struct { + path string + paging *Paging + options *ListOptions +} + +type queryListFiles struct { + input queryListFilesInput + list []interface{} + files [][]interface{} +} + +type queryListFoldersInput struct { + path string + options *ListOptions +} + +type queryListFolders struct { + input queryListFoldersInput + checks [][]interface{} +} + +func interfaceName(myvar interface{}) string { + if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr { + return "*" + t.Elem().Name() + } else { + return t.Name() + } +} + +func handleCommand(t *testing.T, ctx context.Context, cmd interface{}, cmdName string, fs FileStorage) { + t.Helper() + + var err error + var expectedErr *cmdErrorOutput + switch c := cmd.(type) { + case cmdDelete: + err = fs.Delete(ctx, c.path) + if c.error == nil { + require.NoError(t, err, "%s: should be able to delete %s", cmdName, c.path) + } + expectedErr = c.error + case cmdUpsert: + err = fs.Upsert(ctx, &c.cmd) + if c.error == nil { + require.NoError(t, err, "%s: should be able to upsert file %s", cmdName, c.cmd.Path) + } + expectedErr = c.error + case cmdCreateFolder: + err = fs.CreateFolder(ctx, c.path) + if c.error == nil { + require.NoError(t, err, "%s: should be able to create folder %s", cmdName, c.path) + } + expectedErr = c.error + case cmdDeleteFolder: + err = fs.DeleteFolder(ctx, c.path) + if c.error == nil { + require.NoError(t, err, "%s: should be able to delete %s", cmdName, c.path) + } + expectedErr = c.error + default: + t.Fatalf("unrecognized command %s", cmdName) + } + + if expectedErr != nil && err != nil { + if expectedErr.instance != nil { + require.ErrorIs(t, err, expectedErr.instance) + } + + if expectedErr.message != "" { + require.Errorf(t, err, expectedErr.message, expectedErr.args...) + } + } +} + +func runChecks(t *testing.T, stepName string, path string, output interface{}, checks []interface{}) { + if checks == nil || len(checks) == 0 { + return + } + + runFileMetadataCheck := func(file FileMetadata, check interface{}, checkName string) { + switch c := check.(type) { + case filePropertiesCheck: + require.Equal(t, c.v, file.Properties, "%s-%s %s", stepName, checkName, path) + case fileNameCheck: + require.Equal(t, c.v, file.Name, "%s-%s %s", stepName, checkName, path) + case fileSizeCheck: + require.Equal(t, c.v, file.Size, "%s-%s %s", stepName, checkName, path) + case fileMimeTypeCheck: + require.Equal(t, c.v, file.MimeType, "%s-%s %s", stepName, checkName, path) + case filePathCheck: + require.Equal(t, c.v, file.FullPath, "%s-%s %s", stepName, checkName, path) + default: + t.Fatalf("unrecognized file check %s", checkName) + } + } + + switch o := output.(type) { + case File: + for _, check := range checks { + checkName := interfaceName(check) + if fileContentsCheck, ok := check.(fileContentsCheck); ok { + require.Equal(t, fileContentsCheck.v, o.Contents, "%s-%s %s", stepName, checkName, path) + } else { + runFileMetadataCheck(o.FileMetadata, check, checkName) + } + } + case FileMetadata: + for _, check := range checks { + runFileMetadataCheck(o, check, interfaceName(check)) + } + case ListFilesResponse: + for _, check := range checks { + c := check + checkName := interfaceName(c) + switch c := check.(type) { + case listSizeCheck: + require.Equal(t, c.v, len(o.Files), "%s %s", stepName, path) + case listHasMoreCheck: + require.Equal(t, c.v, o.HasMore, "%s %s", stepName, path) + case listLastPathCheck: + require.Equal(t, c.v, o.LastPath, "%s %s", stepName, path) + default: + t.Fatalf("unrecognized list check %s", checkName) + } + } + default: + t.Fatalf("unrecognized output %s", interfaceName(output)) + } + +} + +func formatPathStructure(files []FileMetadata) string { + if len(files) == 0 { + return "<>" + } + res := "\n" + for _, f := range files { + res = fmt.Sprintf("%s%s\n", res, f.FullPath) + } + return res +} + +func handleQuery(t *testing.T, ctx context.Context, query interface{}, queryName string, fs FileStorage) { + t.Helper() + + switch q := query.(type) { + case queryGet: + inputPath := q.input.path + file, err := fs.Get(ctx, inputPath) + require.NoError(t, err, "%s: should be able to get file %s", queryName, inputPath) + + if q.checks != nil && len(q.checks) > 0 { + require.NotNil(t, file, "%s %s", queryName, inputPath) + require.Equal(t, strings.ToLower(inputPath), strings.ToLower(file.FullPath), "%s %s", queryName, inputPath) + runChecks(t, queryName, inputPath, *file, q.checks) + } else { + require.Nil(t, file, "%s %s", queryName, inputPath) + } + case queryListFiles: + inputPath := q.input.path + resp, err := fs.ListFiles(ctx, inputPath, q.input.paging, q.input.options) + require.NoError(t, err, "%s: should be able to list files in %s", queryName, inputPath) + require.NotNil(t, resp) + if q.list != nil && len(q.list) > 0 { + runChecks(t, queryName, inputPath, *resp, q.list) + } else { + require.NotNil(t, resp, "%s %s", queryName, inputPath) + require.Equal(t, false, resp.HasMore, "%s %s", queryName, inputPath) + require.Equal(t, 0, len(resp.Files), "%s %s", queryName, inputPath) + require.Equal(t, "", resp.LastPath, "%s %s", queryName, inputPath) + } + + if q.files != nil { + require.Equal(t, len(resp.Files), len(q.files), "%s expected a check for each actual file at path: \"%s\". actual: %s", queryName, inputPath, formatPathStructure(resp.Files)) + for i, file := range resp.Files { + runChecks(t, queryName, inputPath, file, q.files[i]) + } + } + case queryListFolders: + inputPath := q.input.path + resp, err := fs.ListFolders(ctx, inputPath, q.input.options) + require.NotNil(t, resp) + require.NoError(t, err, "%s: should be able to list folders in %s", queryName, inputPath) + + if q.checks != nil { + require.Equal(t, len(resp), len(q.checks), "%s: expected a check for each actual folder at path: \"%s\". actual: %s", queryName, inputPath, formatPathStructure(resp)) + for i, file := range resp { + runChecks(t, queryName, inputPath, file, q.checks[i]) + } + } else { + require.Equal(t, 0, len(resp), "%s %s", queryName, inputPath) + } + default: + t.Fatalf("unrecognized query %s", queryName) + } +} + +func executeTestStep(t *testing.T, ctx context.Context, step interface{}, stepNumber int, fs FileStorage) { + name := fmt.Sprintf("[%d]%s", stepNumber, interfaceName(step)) + + switch s := step.(type) { + case queryGet: + handleQuery(t, ctx, s, name, fs) + case queryListFiles: + handleQuery(t, ctx, s, name, fs) + case queryListFolders: + handleQuery(t, ctx, s, name, fs) + case cmdUpsert: + handleCommand(t, ctx, s, name, fs) + case cmdDelete: + handleCommand(t, ctx, s, name, fs) + case cmdCreateFolder: + handleCommand(t, ctx, s, name, fs) + case cmdDeleteFolder: + handleCommand(t, ctx, s, name, fs) + default: + t.Fatalf("unrecognized step %s", name) + } + +} diff --git a/pkg/infra/filestorage/wrapper.go b/pkg/infra/filestorage/wrapper.go new file mode 100644 index 00000000000..10e769bc95a --- /dev/null +++ b/pkg/infra/filestorage/wrapper.go @@ -0,0 +1,257 @@ +package filestorage + +import ( + "context" + "fmt" + "mime" + "path/filepath" + "regexp" + "strings" + + "github.com/grafana/grafana/pkg/infra/log" + _ "gocloud.dev/blob/fileblob" + _ "gocloud.dev/blob/memblob" +) + +var ( + directoryMarker = ".___gf_dir_marker___" + pathRegex = regexp.MustCompile(`(^/$)|(^(/[A-Za-z0-9!\-_.*'()]+)+$)`) +) + +type wrapper struct { + log log.Logger + wrapped FileStorage + pathFilters *PathFilters +} + +var ( + _ FileStorage = (*wrapper)(nil) // wrapper implements FileStorage +) + +func getParentFolderPath(path string) string { + if path == Delimiter || path == "" { + return Delimiter + } + + if !strings.Contains(path, Delimiter) { + return Delimiter + } + + split := strings.Split(path, Delimiter) + splitWithoutLastPart := split[:len(split)-1] + if len(splitWithoutLastPart) == 1 && split[0] == "" { + return Delimiter + } + return strings.Join(splitWithoutLastPart, Delimiter) +} + +func getName(path string) string { + if path == Delimiter || path == "" { + return "" + } + + split := strings.Split(path, Delimiter) + return split[len(split)-1] +} + +func validatePath(path string) error { + if !filepath.IsAbs(path) { + return ErrRelativePath + } + + if path == Delimiter { + return nil + } + + if filepath.Clean(path) != path { + return ErrNonCanonicalPath + } + + if strings.HasSuffix(path, Delimiter) { + return ErrPathEndsWithDelimiter + } + + if len(path) > 1000 { + return ErrPathTooLong + } + + matches := pathRegex.MatchString(path) + if !matches { + return ErrPathInvalid + } + + return nil +} + +func (b wrapper) validatePath(path string) error { + if err := validatePath(path); err != nil { + b.log.Error("Path failed validation", "path", path, "error", err) + return err + } + return nil +} + +func (b wrapper) Get(ctx context.Context, path string) (*File, error) { + if err := b.validatePath(path); err != nil { + return nil, err + } + + if !b.pathFilters.isAllowed(path) { + return nil, nil + } + + return b.wrapped.Get(ctx, path) +} +func (b wrapper) Delete(ctx context.Context, path string) error { + if err := b.validatePath(path); err != nil { + return err + } + + if !b.pathFilters.isAllowed(path) { + return nil + } + + return b.wrapped.Delete(ctx, path) +} + +func detectContentType(path string, originalGuess string) string { + if originalGuess == "application/octet-stream" || originalGuess == "" { + mimeTypeBasedOnExt := mime.TypeByExtension(filepath.Ext(path)) + if mimeTypeBasedOnExt == "" { + return "application/octet-stream" + } + return mimeTypeBasedOnExt + } + return originalGuess +} + +func (b wrapper) Upsert(ctx context.Context, file *UpsertFileCommand) error { + if err := b.validatePath(file.Path); err != nil { + return err + } + + if !b.pathFilters.isAllowed(file.Path) { + return nil + } + + path := getParentFolderPath(file.Path) + b.log.Info("Creating folder before upserting file", "file", file.Path, "folder", path) + if err := b.CreateFolder(ctx, path); err != nil { + return err + } + + if file.Contents != nil && file.MimeType == "" { + file.MimeType = detectContentType(file.Path, "") + } + + return b.wrapped.Upsert(ctx, file) +} + +func (b wrapper) withDefaults(options *ListOptions, folderQuery bool) *ListOptions { + if options == nil { + options = &ListOptions{} + options.Recursive = folderQuery + if b.pathFilters != nil && b.pathFilters.allowedPrefixes != nil { + options.PathFilters = *b.pathFilters + } + + return options + } + + if b.pathFilters != nil && b.pathFilters.allowedPrefixes != nil { + if options.allowedPrefixes != nil { + options.allowedPrefixes = append(options.allowedPrefixes, b.pathFilters.allowedPrefixes...) + } else { + copiedPrefixes := make([]string, len(b.pathFilters.allowedPrefixes)) + copy(copiedPrefixes, b.pathFilters.allowedPrefixes) + options.allowedPrefixes = copiedPrefixes + } + } + + return options +} + +func (b wrapper) ListFiles(ctx context.Context, path string, paging *Paging, options *ListOptions) (*ListFilesResponse, error) { + if err := b.validatePath(path); err != nil { + return nil, err + } + + if paging == nil { + paging = &Paging{ + First: 100, + } + } else if paging.First <= 0 { + paging.First = 100 + } + + return b.wrapped.ListFiles(ctx, path, paging, b.withDefaults(options, false)) +} + +func (b wrapper) ListFolders(ctx context.Context, path string, options *ListOptions) ([]FileMetadata, error) { + if err := b.validatePath(path); err != nil { + return nil, err + } + + return b.wrapped.ListFolders(ctx, path, b.withDefaults(options, true)) +} + +func (b wrapper) CreateFolder(ctx context.Context, path string) error { + if err := b.validatePath(path); err != nil { + return err + } + + if !b.pathFilters.isAllowed(path) { + return nil + } + + return b.wrapped.CreateFolder(ctx, path) +} + +func (b wrapper) DeleteFolder(ctx context.Context, path string) error { + if err := b.validatePath(path); err != nil { + return err + } + + if !b.pathFilters.isAllowed(path) { + return nil + } + + isEmpty, err := b.isFolderEmpty(ctx, path) + if err != nil { + return err + } + + if !isEmpty { + return fmt.Errorf("folder %s is not empty - cant remove it", path) + } + + return b.wrapped.DeleteFolder(ctx, path) +} + +func (b wrapper) isFolderEmpty(ctx context.Context, path string) (bool, error) { + filesInFolder, err := b.ListFiles(ctx, path, &Paging{First: 1}, &ListOptions{Recursive: true}) + if err != nil { + return false, err + } + + if len(filesInFolder.Files) > 0 { + return false, nil + } + + folders, err := b.ListFolders(ctx, path, &ListOptions{ + Recursive: true, + }) + if err != nil { + return false, err + } + + if len(folders) > 0 { + return false, nil + } + + return true, nil +} + +func (b wrapper) close() error { + return b.wrapped.close() +} diff --git a/pkg/plugins/manager/signature/manifest.go b/pkg/plugins/manager/signature/manifest.go index 3a852046421..43e474af440 100644 --- a/pkg/plugins/manager/signature/manifest.go +++ b/pkg/plugins/manager/signature/manifest.go @@ -15,7 +15,10 @@ import ( "path/filepath" "strings" + // TODO: replace deprecated `golang.org/x/crypto` package https://github.com/grafana/grafana/issues/46050 + // nolint:staticcheck "golang.org/x/crypto/openpgp" + // nolint:staticcheck "golang.org/x/crypto/openpgp/clearsign" "github.com/grafana/grafana/pkg/infra/log" diff --git a/pkg/server/wire.go b/pkg/server/wire.go index ab8daa4d39a..530d0f43806 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/expr" + "github.com/grafana/grafana/pkg/infra/filestorage" "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider" "github.com/grafana/grafana/pkg/infra/kvstore" @@ -146,6 +147,7 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), quota.ProvideService, remotecache.ProvideService, + filestorage.ProvideService, loginservice.ProvideService, wire.Bind(new(login.Service), new(*loginservice.Implementation)), authinfoservice.ProvideAuthInfoService, diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index f0a542c248f..1d02d449c7e 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -162,5 +162,11 @@ var ( Description: "Lock database during migrations", State: FeatureStateBeta, }, + { + Name: "fileStoreApi", + Description: "Simple API for managing files", + State: FeatureStateAlpha, + RequiresDevMode: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index bb15e26e660..51aeb9bec63 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -122,4 +122,8 @@ const ( // FlagMigrationLocking // Lock database during migrations FlagMigrationLocking = "migrationLocking" + + // FlagFileStoreApi + // Simple API for managing files + FlagFileStoreApi = "fileStoreApi" ) diff --git a/pkg/services/sqlstore/migrations/db_file_storage.go b/pkg/services/sqlstore/migrations/db_file_storage.go new file mode 100644 index 00000000000..c5cd1bc30bc --- /dev/null +++ b/pkg/services/sqlstore/migrations/db_file_storage.go @@ -0,0 +1,41 @@ +package migrations + +import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + +// TODO: remove nolint as part of https://github.com/grafana/grafana/issues/45498 +// nolint:unused,deadcode +func addDbFileStorageMigration(mg *migrator.Migrator) { + filesTable := migrator.Table{ + Name: "file", + Columns: []*migrator.Column{ + {Name: "path", Type: migrator.DB_NVarchar, Length: 1024, Nullable: false}, + {Name: "parent_folder_path", Type: migrator.DB_NVarchar, Length: 1024, Nullable: false}, + {Name: "contents", Type: migrator.DB_Blob, Nullable: false}, + {Name: "updated", Type: migrator.DB_DateTime, Nullable: false}, + {Name: "created", Type: migrator.DB_DateTime, Nullable: false}, + {Name: "size", Type: migrator.DB_BigInt, Nullable: false}, + {Name: "mime_type", Type: migrator.DB_NVarchar, Length: 255, Nullable: false}, + }, + Indices: []*migrator.Index{ + {Cols: []string{"path"}, Type: migrator.UniqueIndex}, + }, + } + + mg.AddMigration("create file table", migrator.NewAddTableMigration(filesTable)) + mg.AddMigration("file table idx: path natural pk", migrator.NewAddIndexMigration(filesTable, filesTable.Indices[0])) + + fileMetaTable := migrator.Table{ + Name: "file_meta", + Columns: []*migrator.Column{ + {Name: "path", Type: migrator.DB_NVarchar, Length: 1024, Nullable: false}, + {Name: "key", Type: migrator.DB_NVarchar, Length: 1024, Nullable: false}, + {Name: "value", Type: migrator.DB_NVarchar, Length: 1024, Nullable: false}, + }, + Indices: []*migrator.Index{ + {Cols: []string{"path", "key"}, Type: migrator.UniqueIndex}, + }, + } + + mg.AddMigration("create file_meta table", migrator.NewAddTableMigration(fileMetaTable)) + mg.AddMigration("file table idx: path key", migrator.NewAddIndexMigration(fileMetaTable, fileMetaTable.Indices[0])) +}