mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
FileStore: add basic file storage API (#46051)
* #45498: fs API alpha * #45498: remove grafanaDS changes for filestorage.go * #45498: fix lint * #45498: fix lint * #45498: remove db file storage migration * #45498: linting * #45498: linting * #45498: linting * #45498: fix imports * #45498: add comment * remove StorageName abstractions * FileStore: add dummy implementation (#46071) * #45498: bring back grafanaDs changes, add dummy filestorage * #45498: rename grafanaDs to public * #45498: modify join * #45498: review fix * #45498: unnecessary leading newline (whitespace) IMPORTANT FIX * #45498: fix belongsToStorage * #45498: fix removeStoragePrefix so that it works with abs paths Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
854f872b40
commit
a8b90d9a25
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@ -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
|
||||
|
53
go.mod
53
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
|
||||
|
187
go.sum
187
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=
|
||||
|
@ -46,4 +46,5 @@ export interface FeatureToggles {
|
||||
dashboardComments?: boolean;
|
||||
annotationComments?: boolean;
|
||||
migrationLocking?: boolean;
|
||||
fileStoreApi?: boolean;
|
||||
}
|
||||
|
101
pkg/infra/filestorage/api.go
Normal file
101
pkg/infra/filestorage/api.go
Normal file
@ -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
|
||||
}
|
75
pkg/infra/filestorage/api_test.go
Normal file
75
pkg/infra/filestorage/api_test.go
Normal file
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
491
pkg/infra/filestorage/cdk_blob_filestorage.go
Normal file
491
pkg/infra/filestorage/cdk_blob_filestorage.go
Normal file
@ -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()
|
||||
}
|
449
pkg/infra/filestorage/db_filestorage.go
Normal file
449
pkg/infra/filestorage/db_filestorage.go
Normal file
@ -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
|
||||
}
|
51
pkg/infra/filestorage/dummy.go
Normal file
51
pkg/infra/filestorage/dummy.go
Normal file
@ -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
|
||||
}
|
160
pkg/infra/filestorage/filestorage.go
Normal file
160
pkg/infra/filestorage/filestorage.go
Normal file
@ -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()
|
||||
}
|
46
pkg/infra/filestorage/filestorage_test.go
Normal file
46
pkg/infra/filestorage/filestorage_test.go
Normal file
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
857
pkg/infra/filestorage/fs_integration_test.go
Normal file
857
pkg/infra/filestorage/fs_integration_test.go
Normal file
@ -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)
|
||||
}
|
346
pkg/infra/filestorage/test_utils.go
Normal file
346
pkg/infra/filestorage/test_utils.go
Normal file
@ -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 "<<EMPTY>>"
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
257
pkg/infra/filestorage/wrapper.go
Normal file
257
pkg/infra/filestorage/wrapper.go
Normal file
@ -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()
|
||||
}
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -162,5 +162,11 @@ var (
|
||||
Description: "Lock database during migrations",
|
||||
State: FeatureStateBeta,
|
||||
},
|
||||
{
|
||||
Name: "fileStoreApi",
|
||||
Description: "Simple API for managing files",
|
||||
State: FeatureStateAlpha,
|
||||
RequiresDevMode: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -122,4 +122,8 @@ const (
|
||||
// FlagMigrationLocking
|
||||
// Lock database during migrations
|
||||
FlagMigrationLocking = "migrationLocking"
|
||||
|
||||
// FlagFileStoreApi
|
||||
// Simple API for managing files
|
||||
FlagFileStoreApi = "fileStoreApi"
|
||||
)
|
||||
|
41
pkg/services/sqlstore/migrations/db_file_storage.go
Normal file
41
pkg/services/sqlstore/migrations/db_file_storage.go
Normal file
@ -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]))
|
||||
}
|
Loading…
Reference in New Issue
Block a user