From 2d364948f15815e3b036021d8d6e0c49bffb4b42 Mon Sep 17 00:00:00 2001 From: "x.zhou" Date: Mon, 25 Dec 2023 12:16:48 +0800 Subject: [PATCH] feat: integrate with kopia --- cmd/app.go | 80 +++--- go.mod | 77 +++--- go.sum | 278 ++++++++++----------- pkg/config/global.go | 5 +- pkg/storage/kopia/blobstorageimpl.go | 167 +++++++++++++ pkg/storage/kopia/kopia.go | 351 +++++++++++++++++++++++++++ pkg/storage/kopia/maintenance.go | 64 +++++ pkg/storage/kopia/progress.go | 88 +++++++ pkg/storage/kopia/repo.go | 188 ++++++++++++++ pkg/storage/kopia/snapshot.go | 196 +++++++++++++++ pkg/storage/kopia/underlying.go | 15 ++ pkg/storage/rclone/rclone.go | 108 +++++++-- pkg/storage/sanitized/sanitized.go | 171 +++++++++++++ pkg/storage/types.go | 28 ++- pkg/util/errors.go | 17 ++ 15 files changed, 1587 insertions(+), 246 deletions(-) create mode 100644 pkg/storage/kopia/blobstorageimpl.go create mode 100644 pkg/storage/kopia/kopia.go create mode 100644 pkg/storage/kopia/maintenance.go create mode 100644 pkg/storage/kopia/progress.go create mode 100644 pkg/storage/kopia/repo.go create mode 100644 pkg/storage/kopia/snapshot.go create mode 100644 pkg/storage/kopia/underlying.go create mode 100644 pkg/storage/sanitized/sanitized.go create mode 100644 pkg/util/errors.go diff --git a/cmd/app.go b/cmd/app.go index 44ed24e..133e59e 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -4,7 +4,7 @@ import ( "context" "fmt" "os" - "path/filepath" + "strconv" "strings" "github.com/spf13/cobra" @@ -12,12 +12,17 @@ import ( "github.com/apecloud/datasafed/pkg/config" "github.com/apecloud/datasafed/pkg/logging" "github.com/apecloud/datasafed/pkg/storage" + "github.com/apecloud/datasafed/pkg/storage/kopia" "github.com/apecloud/datasafed/pkg/storage/rclone" ) const ( - rootKey = "root" - backendBasePathEnv = "DATASAFED_BACKEND_BASE_PATH" + backendBasePathEnv = "DATASAFED_BACKEND_BASE_PATH" + kopiaRepoRootEnv = "DATASAFED_KOPIA_REPO_ROOT" + kopiaPasswordEnv = "DATASAFED_KOPIA_PASSWORD" + kopiaDisableCacheEnv = "DATASAFED_KOPIA_DISABLE_CACHE" + kopiaMaintenanceEnv = "DATASAFED_KOPIA_MAINTENANCE" + kopiaSafetyEnv = "DATASAFED_KOPIA_SAFETY" ) var ( @@ -75,39 +80,58 @@ func initStorage() error { return err } + basePath := strings.TrimSpace(os.Getenv(backendBasePathEnv)) storageConf := config.GetGlobal().GetAll(config.StorageSection) - adjustRoot(storageConf) - var err error - globalStorage, err = rclone.New(storageConf) - if err != nil { - return err + + if kopiaRoot := strings.TrimSpace(os.Getenv(kopiaRepoRootEnv)); kopiaRoot != "" { + return initKopiaStorage(storageConf, basePath, kopiaRoot) + } else { + st, err := createStorage(storageConf, basePath) + if err != nil { + return err + } + globalStorage = st + return nil } - return nil } -func adjustRoot(storageConf map[string]string) { - basePath := os.Getenv(backendBasePathEnv) - if basePath == "" { - return +func initKopiaStorage(storageConf map[string]string, basePath, kopiaRoot string) error { + underlying, err := createStorage(storageConf, "") + if err != nil { + return err } - basePath = filepath.Clean(basePath) - if strings.HasPrefix(basePath, "..") { - exitIfError(fmt.Errorf("invalid base path %q from env %s", - os.Getenv(backendBasePathEnv), backendBasePathEnv)) + kopia.SetUnderlyingStorage(underlying) + storageConf[kopia.RepoRootKey] = kopiaRoot + storageConf[kopia.PasswordKey] = strings.TrimSpace(os.Getenv(kopiaPasswordEnv)) + storageConf[kopia.DisableCacheKey] = strings.TrimSpace(os.Getenv(kopiaDisableCacheEnv)) + st, err := kopia.New(appCtx, storageConf, basePath) + if err != nil { + return err } - if basePath == "." { - basePath = "" - } else { - basePath = strings.TrimPrefix(basePath, "/") - basePath = strings.TrimPrefix(basePath, "./") + globalStorage = st + + maintenance := os.Getenv(kopiaMaintenanceEnv) + if ok, _ := strconv.ParseBool(maintenance); ok { + onFinish(func() { + err := kopia.RunMaintenance(appCtx, globalStorage, os.Getenv(kopiaSafetyEnv)) + if err != nil { + fmt.Fprintf(os.Stderr, "RunMaintenance() failed, err: %v\n", err) + } + }) } - root := storageConf[rootKey] - if strings.HasSuffix(root, "/") { - root = root + basePath - } else { - root = root + "/" + basePath + return nil +} + +func createStorage(conf map[string]string, basePath string) (storage.Storage, error) { + cloneConf := make(map[string]string, len(conf)) + for k, v := range conf { + cloneConf[k] = v } - storageConf[rootKey] = root + return rclone.New(appCtx, cloneConf, basePath) +} + +func onFinish(fn func()) { + onFinishFuncs = append(onFinishFuncs, fn) } func Execute() { diff --git a/go.mod b/go.mod index 97a07ba..7c8763d 100644 --- a/go.mod +++ b/go.mod @@ -4,22 +4,23 @@ go 1.21 require ( github.com/fatih/color v1.16.0 + github.com/kopia/kopia v0.15.0 github.com/rclone/rclone v1.63.1 github.com/spf13/cobra v1.7.0 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.26.0 gopkg.in/ini.v1 v1.67.0 ) require ( - cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/abbot/go-http-auth v0.4.0 // indirect @@ -28,24 +29,31 @@ require ( github.com/buengese/sgzip v0.1.1 // indirect github.com/calebcase/tmpfile v1.0.3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chmduquesne/rollinghash v4.0.0+incompatible // indirect github.com/colinmarc/hdfs/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5 // indirect + github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/go-chi/chi/v5 v5.0.8 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.8.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect @@ -61,40 +69,43 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b // indirect github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect - github.com/klauspost/compress v1.16.5 // indirect - github.com/klauspost/cpuid/v2 v2.0.12 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect + github.com/klauspost/reedsolomon v1.11.8 // indirect github.com/koofr/go-httpclient v0.0.0-20230225102643-5d51a2e9dea6 // indirect github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6 // indirect github.com/kr/fs v0.1.0 // indirect - github.com/kr/pretty v0.3.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/natefinch/atomic v1.0.1 // indirect github.com/ncw/go-acd v0.0.0-20201019170801-fe55f33415b1 // indirect github.com/ncw/swift/v2 v2.0.1 // indirect github.com/oracle/oci-go-sdk/v65 v65.34.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 // indirect + github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect - github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/sftp v1.13.6 // indirect github.com/pkg/xattr v0.4.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8 // indirect github.com/rclone/ftp v0.0.0-20230327202000-dadc1f64e87d // indirect github.com/rfjakob/eme v1.1.2 // indirect - github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil/v3 v3.23.5 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/spacemonkeygo/monkit/v3 v3.0.19 // indirect @@ -111,20 +122,24 @@ require ( github.com/zeebo/errs v1.3.0 // indirect go.etcd.io/bbolt v1.3.7 // indirect go.opencensus.io v0.24.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect - golang.org/x/sync v0.1.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.14.0 // indirect - golang.org/x/term v0.7.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/api v0.115.0 // indirect + google.golang.org/api v0.146.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/grpc v1.58.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect storj.io/common v0.0.0-20221123115229-fed3e6651b63 // indirect diff --git a/go.sum b/go.sum index 1384ceb..c0b8dce 100644 --- a/go.sum +++ b/go.sum @@ -13,21 +13,18 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 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/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= 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= @@ -38,20 +35,24 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl 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= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 h1:9kDVnTz3vbfweTqAUmk/a/pH5pWFCHtvRpHYC0G/dcA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 h1:LNHhpdK7hzUcx/k1LIcuh5k7k1LGIWLQfCjaneSj7Fc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= -github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI= +github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec= github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE= github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= @@ -62,15 +63,10 @@ github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3 h1:hhdWprfSpFbN7lz3W github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3/go.mod h1:XaUnRxSCYgL3kkgX0QHIV0D+znljPIDImxlv2kbGv0Y= github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0= github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/aws/aws-sdk-go v1.44.246 h1:iLxPX6JU0bxAci9R6/bp8rX0kL871ByCTx0MZlQWv1U= github.com/aws/aws-sdk-go v1.44.246/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buengese/sgzip v0.1.1 h1:ry+T8l1mlmiWEsDrH/YHZnCVWD2S3im1KLsyO+8ZmTU= @@ -78,10 +74,10 @@ github.com/buengese/sgzip v0.1.1/go.mod h1:i5ZiXGF3fhV7gL1xaRRL1nDnmpNj0X061FQzO github.com/calebcase/tmpfile v1.0.3 h1:BZrOWZ79gJqQ3XbAQlihYZf/YCV0H4KPIdM5K5oMpJo= github.com/calebcase/tmpfile v1.0.3/go.mod h1:UAUc01aHeC+pudPagY/lWvt2qS9ZO5Zzof6/tIUzqeI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chmduquesne/rollinghash v4.0.0+incompatible h1:hnREQO+DXjqIw3rUTzWN7/+Dpw+N5Um8zpKV0JOEgbo= +github.com/chmduquesne/rollinghash v4.0.0+incompatible/go.mod h1:Uc2I36RRfTAf7Dge82bi3RU0OQUmXT9iweIcPqvr8A0= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -93,7 +89,8 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -101,12 +98,16 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5 h1:FT+t0UEDykcor4y3dMVKXIiWJETBpRgERYTGlmMd7HU= github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5/go.mod h1:rSS3kM9XMzSQ6pw91Qgd6yB5jdt70N4OdtrAf74As5M= +github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= +github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/frankban/quicktest v1.13.1 h1:xVm/f9seEhZFL9+n5kv5XLrGwy6elc4V9v/XFY2vmd8= +github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= @@ -118,25 +119,24 @@ github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITL github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -165,7 +165,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -179,14 +178,12 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -196,22 +193,30 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20211108044417-e9b028704de0 h1:rsq1yB2xiFLDYYaYdlGBsSkwVzsCo500wMhxvW5A/bk= -github.com/google/pprof v0.0.0-20211108044417-e9b028704de0/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= -github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hanwen/go-fuse/v2 v2.4.0 h1:12OhD7CkXXQdvxG2osIdBQLdXh+nmLXY9unkUIe/xaU= +github.com/hanwen/go-fuse/v2 v2.4.0/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -244,37 +249,36 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b h1:tO4MX3k5bvV0Sjv5jYrxStMTJxf1m/TW24XRyHji4aU= github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b/go.mod h1:q7yMR8BavTz/gBNtIT/uF487LMgcuEpNGKISLAjNQes= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC9jFiTxyptEKuNIAbiN5ZCQzX2a74lj3xg= github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= +github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= github.com/koofr/go-httpclient v0.0.0-20230225102643-5d51a2e9dea6 h1:uF5FHZ/L5gvZTyBNhhcm55rRorL66DOs4KIeeVXZ8eI= github.com/koofr/go-httpclient v0.0.0-20230225102643-5d51a2e9dea6/go.mod h1:6HAT62hK6QH+ljNtZayJCKpbZy5hJIB12+1Ze1bFS7M= github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6 h1:FHVoZMOVRA+6/y4yRlbiR3WvsrOcKBd/f64H7YiWR2U= github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6/go.mod h1:MRAz4Gsxd+OzrZ0owwrUHc0zLESL+1Y5syqK/sJxK2A= +github.com/kopia/htmluibuild v0.0.1-0.20231019063300-75c2a788c7d0 h1:TvupyyfbUZzsO4DQJpQhKZnUa61xERcJ+ejCbHWG2NY= +github.com/kopia/htmluibuild v0.0.1-0.20231019063300-75c2a788c7d0/go.mod h1:cSImbrlwvv2phvj5RfScL2v08ghX6xli0PcK6f+t8S0= +github.com/kopia/kopia v0.15.0 h1:H+nJwFhxP0fqmrQQHJwBF6uUEKyU0Otij0nNKYBYhoM= +github.com/kopia/kopia v0.15.0/go.mod h1:V/zpEMjxzqEf3lF52m0b0nAIVQGolPYIOXRjVxeK1j0= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -288,18 +292,13 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/ncw/go-acd v0.0.0-20201019170801-fe55f33415b1 h1:nAjWYc03awJAjsozNehdGZsm5LP7AhLOvjgbS8zN1tk= github.com/ncw/go-acd v0.0.0-20201019170801-fe55f33415b1/go.mod h1:MLIrzg7gp/kzVBxRE1olT7CWYMCklcUWU+ekoxOD9x0= github.com/ncw/swift/v2 v2.0.1 h1:q1IN8hNViXEv8Zvg3Xdis4a3c4IlIGezkYz09zQL5J0= @@ -316,46 +315,29 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 h1:XeOYlK9W1uCmhjJSsY78Mcuh7MVkNjTzmHx1yBzizSU= github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14/go.mod h1:jVblp62SafmidSkvWrXyxAme3gaTfEtWwRPGz5cpvHg= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6 h1:5TvW1dv00Y13njmQ1AWkxSWtPkwE7ZEF6yDuv9q+Als= -github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8 h1:Y258uzXU/potCYnQd1r6wlAnoMB68BiCkCcCnKx1SH8= github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8/go.mod h1:bSJjRokAHHOhA+XFxplld8w2R/dXLH7Z3BZ532vhFwU= github.com/rclone/ftp v0.0.0-20230327202000-dadc1f64e87d h1:ZyH6ZfA/PzxF4qQS2MgFLXRdw/pWOSNJA7Lq0pkX49Y= @@ -365,9 +347,8 @@ github.com/rclone/rclone v1.63.1/go.mod h1:eUQaKsf1wJfHKB0RDoM8RaPAeRB2eI/Qw+Vc9 github.com/rfjakob/eme v1.1.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4= github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v3 v3.23.5 h1:5SgDCeQ0KW0S4N0znjeM/eFHXXOKyv2dVNgRq/c9P6Y= @@ -376,12 +357,9 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= @@ -393,7 +371,6 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -406,10 +383,13 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA= github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA= +github.com/tg123/go-htpasswd v1.2.1 h1:i4wfsX1KvvkyoMiHZzjS0VzbAPWfxzI8INcZAKtutoU= +github.com/tg123/go-htpasswd v1.2.1/go.mod h1:erHp1B86KXdwQf1X5ZrLb7erXZnWueEQezb2dql4q58= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= @@ -429,6 +409,8 @@ github.com/yunify/qingstor-sdk-go/v3 v3.2.0 h1:9sB2WZMgjwSUNZhrgvaNGazVltoFUUfuS github.com/yunify/qingstor-sdk-go/v3 v3.2.0/go.mod h1:KciFNuMu6F4WLk9nGwwK69sCGKLCdd9f97ac/wfumS4= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= +github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A= github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -447,13 +429,18 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -465,8 +452,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -477,6 +464,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -500,7 +489,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -508,7 +496,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -530,26 +517,21 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= 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= @@ -559,19 +541,15 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -582,7 +560,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -595,8 +572,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -605,11 +580,8 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -626,8 +598,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -637,8 +609,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -707,8 +679,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.115.0 h1:6FFkVvStt4YqXSx3azKyzj7fXerGnVlLJ/eud01nBDE= -google.golang.org/api v0.115.0/go.mod h1:9cD4/t6uvd9naoEJFA+M96d0IuB6BqFuyhpw68+mRGg= +google.golang.org/api v0.146.0 h1:9aBYT4vQXt9dhCuLNfwfd3zpwu8atg0yPkjBymwSrOM= +google.golang.org/api v0.146.0/go.mod h1:OARJqIfoYjXJj4C1AiBSXYZt03qsoz8FQYU6fBEfrHM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -746,8 +718,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 h1:0BOZf6qNozI3pkN3fJLwNubheHJYHhMh91GRFOWWK08= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= +google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -761,8 +737,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -775,12 +751,10 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -788,12 +762,8 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/config/global.go b/pkg/config/global.go index 28a94e9..f217efe 100644 --- a/pkg/config/global.go +++ b/pkg/config/global.go @@ -25,8 +25,9 @@ func InitGlobal(configFile string) error { if localBackendPath != "" { global, err = NewStaticConfig(map[string]map[string]string{ StorageSection: { - "type": "local", - "root": localBackendPath, + "type": "local", + "copy_links": "true", + "root": localBackendPath, }, }) } else { diff --git a/pkg/storage/kopia/blobstorageimpl.go b/pkg/storage/kopia/blobstorageimpl.go new file mode 100644 index 0000000..75e2ca9 --- /dev/null +++ b/pkg/storage/kopia/blobstorageimpl.go @@ -0,0 +1,167 @@ +package kopia + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "time" + + "github.com/apecloud/datasafed/pkg/storage" + "github.com/kopia/kopia/repo/blob" + "github.com/kopia/kopia/repo/blob/sharded" +) + +const ( + storageType = "datasafed" +) + +func init() { + blob.AddSupportedStorage(storageType, blobOptions{}, newBlobStorage) +} + +type blobOptions struct { + RootPath string `json:"root_path"` + Caching bool `json:"caching"` +} + +type blobStorageImpl struct { + sharded.Storage + blob.DefaultProviderImplementation + + s storage.Storage +} + +var _ blob.Storage = (*blobStorageImpl)(nil) +var _ sharded.Impl = (*blobStorageImpl)(nil) + +func newBlobStorage(ctx context.Context, options *blobOptions, isCreate bool) (blob.Storage, error) { + underlying := GetUnderlyingStorage() + if underlying == nil { + return nil, fmt.Errorf("SetUnderlyingStorage() should be called first") + } + impl := &blobStorageImpl{s: underlying} + impl.Storage = sharded.New(impl, options.RootPath, sharded.Options{ListParallelism: 8}, isCreate) + return impl, nil +} + +func (b *blobStorageImpl) GetBlobFromPath(ctx context.Context, dirPath, filePath string, offset, length int64, output blob.OutputBuffer) error { + log(ctx).Debugf("GetBlobFromPath dir %s file %s, offset %d length %d", dirPath, filePath, offset, length) + rd, err := b.s.OpenFile(ctx, filePath, offset, length) + if err != nil { + if errors.Is(err, storage.ErrObjectNotFound) { + return blob.ErrBlobNotFound + } + return fmt.Errorf("fail to OpenFile, err: %w", err) + } + defer rd.Close() + _, err = io.Copy(output, rd) + if err != nil { + return fmt.Errorf("fail to copy, err: %w", err) + } + return blob.EnsureLengthExactly(output.Length(), length) +} + +func (b *blobStorageImpl) GetMetadataFromPath(ctx context.Context, dirPath, filePath string) (blob.Metadata, error) { + log(ctx).Debugf("GetMetadata dir %s file %s", dirPath, filePath) + entries, err := b.s.List(ctx, filePath, &storage.ListOptions{PathIsFile: true}) + if err != nil { + if errors.Is(err, storage.ErrObjectNotFound) { + return blob.Metadata{}, blob.ErrBlobNotFound + } + return blob.Metadata{}, fmt.Errorf("fail to OpenFile, err: %w", err) + } + if len(entries) != 1 { + return blob.Metadata{}, fmt.Errorf("expect List() to return single entry for path %s, got %d", filePath, len(entries)) + } + entry := entries[0] + return blob.Metadata{ + Length: entry.Size(), + Timestamp: entry.MTime(), + }, nil +} + +func (b *blobStorageImpl) PutBlobInPath(ctx context.Context, dirPath, filePath string, dataSlices blob.Bytes, opts blob.PutOptions) error { + log(ctx).Debugf("PutBlobInPath dir %s file %s", dirPath, filePath) + switch { + case opts.HasRetentionOptions(): + return fmt.Errorf("%w: blob-retention", blob.ErrUnsupportedPutBlobOption) + case opts.DoNotRecreate: + return fmt.Errorf("%w: do-not-recreate", blob.ErrUnsupportedPutBlobOption) + case !opts.SetModTime.IsZero(): + return blob.ErrSetTimeUnsupported + } + + // according to this post https://forum.rclone.org/t/are-transfers-atomic/21704/6 , + // the transfer is atomic for most of the backends supported by rclone. + err := b.s.Push(ctx, dataSlices.Reader(), filePath) + + if opts.GetModTime != nil { + bm, err2 := b.GetMetadataFromPath(ctx, dirPath, filePath) + if err2 != nil { + return err2 + } + + *opts.GetModTime = bm.Timestamp + } + return err +} + +func (b *blobStorageImpl) DeleteBlobInPath(ctx context.Context, dirPath, filePath string) error { + log(ctx).Debugf("DeleteBlobInPath dir %s file %s", dirPath, filePath) + return b.s.Remove(ctx, filePath, false) +} + +func (b *blobStorageImpl) ReadDir(ctx context.Context, path string) ([]os.FileInfo, error) { + log(ctx).Debugf("ReadDir path %s", path) + var fileInfos []os.FileInfo + _, err := b.s.List(ctx, path, &storage.ListOptions{ + Recursive: false, + Callback: func(en storage.DirEntry) error { + info := fileInfo{ + name: en.Name(), + size: en.Size(), + modTime: en.MTime(), + isDir: en.IsDir(), + } + fileInfos = append(fileInfos, &info) + return nil + }, + }) + return fileInfos, err +} + +func (b *blobStorageImpl) ConnectionInfo() blob.ConnectionInfo { + return blob.ConnectionInfo{ + Type: storageType, + Config: blobOptions{ + RootPath: b.RootPath, + }, + } +} + +func (b *blobStorageImpl) DisplayName() string { + return storageType +} + +type fileInfo struct { + name string + size int64 + modTime time.Time + isDir bool +} + +func (f *fileInfo) Name() string { return f.name } +func (f *fileInfo) Size() int64 { return f.size } +func (f *fileInfo) ModTime() time.Time { return f.modTime } +func (f *fileInfo) IsDir() bool { return f.isDir } +func (f *fileInfo) Sys() any { return nil } + +func (f *fileInfo) Mode() os.FileMode { + if f.isDir { + return 0755 | os.ModeDir + } else { + return 0644 + } +} diff --git a/pkg/storage/kopia/kopia.go b/pkg/storage/kopia/kopia.go new file mode 100644 index 0000000..4e06c2b --- /dev/null +++ b/pkg/storage/kopia/kopia.go @@ -0,0 +1,351 @@ +package kopia + +import ( + "bytes" + "context" + "crypto/md5" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/repo/manifest" + "github.com/kopia/kopia/snapshot" + + "github.com/apecloud/datasafed/pkg/logging" + "github.com/apecloud/datasafed/pkg/storage" + "github.com/apecloud/datasafed/pkg/storage/sanitized" + "github.com/apecloud/datasafed/pkg/util" +) + +const ( + RepoRootKey = "kopia.repo_root" + PasswordKey = "kopia.password" + DisableCacheKey = "kopia.disable_cache" + + metaSuffix = ".meta" + tmpSuffix = ".tmp" +) + +var log = logging.Module("storage/kopia") + +type meta struct { + Name string `json:"name"` + Size int64 `json:"size"` + ModTime time.Time `json:"mod_time"` + SnapshotID string `json:"snapshot_id"` +} + +type kopiaStorage struct { + rep repo.Repository + underlying storage.Storage +} + +var _ storage.Storage = (*kopiaStorage)(nil) + +func New(ctx context.Context, cfg map[string]string, basePath string) (storage.Storage, error) { + underlying := GetUnderlyingStorage() + if underlying == nil { + return nil, fmt.Errorf("SetUnderlyingStorage() should be called first") + } + + repoRootPath := cfg[RepoRootKey] + repoRootPath = filepath.Clean(repoRootPath) + if repoRootPath == "." || repoRootPath == "/" || strings.HasPrefix(repoRootPath, "..") { + return nil, fmt.Errorf("kopia repo root path should not be '.', '/' or started with '..', path: %q", repoRootPath) + } + + // after filepath.Clean(), repoRootPath should not contain '/' at the end + repoMetaPath := repoRootPath + metaSuffix + underlying, err := sanitized.New(ctx, repoMetaPath, underlying) + if err != nil { + return nil, fmt.Errorf("sanitized.New error: %w", err) + } + + cacheID := "" + if disabled, _ := strconv.ParseBool(cfg[DisableCacheKey]); !disabled { + cacheID = generateUniqueID(cfg, basePath) + } + + password := cfg[PasswordKey] + rep, err := getInitedRepository(ctx, repoRootPath, password, cacheID) + if err != nil { + return nil, fmt.Errorf("getInitedRepository error: %w", err) + } + s := &kopiaStorage{ + rep: rep, + underlying: underlying, + } + return sanitized.New(ctx, basePath, s) +} + +func generateUniqueID(cfg map[string]string, basePath string) string { + data, _ := json.Marshal(cfg) + hash := md5.New() + hash.Write(data) + hash.Write([]byte(basePath)) + return hex.EncodeToString(hash.Sum(nil)) +} + +func (s *kopiaStorage) Push(ctx context.Context, r io.Reader, rpath string) error { + log(ctx).Infof("[KOPIA] Push %s", rpath) + + fileName := filepath.Base(rpath) + if fileName == "" || fileName == "." { + return fmt.Errorf("invalid file name: %q", fileName) + } + + // save the old meta file if exists + var oldMetaFile string + content, err := s.underlying.OpenFile(ctx, rpath+metaSuffix, 0, -1) + if err == nil { + defer content.Close() + oldMetaFile = rpath + tmpSuffix + err := s.underlying.Push(ctx, content, oldMetaFile+metaSuffix) + if err != nil { + return fmt.Errorf("unable to save old meta file: %w", err) + } + } else { + if !errors.Is(err, storage.ErrObjectNotFound) { + return fmt.Errorf("unable to open meta file: %w", err) + } + } + + // add the file to the kopia repo + var manifest *snapshot.Manifest + err = repo.WriteSession(ctx, s.rep, repo.WriteSessionOptions{ + Purpose: "datasafed:push", + }, func(ctx context.Context, w repo.RepositoryWriter) error { + var err error + manifest, err = snapshotSingleFile(ctx, fileName, r, w, nil) + return err + }) + if err != nil { + return err + } + + // write meta file + meta := &meta{ + Name: fileName, + Size: manifest.Stats.TotalFileSize, + ModTime: manifest.EndTime.ToTime(), + SnapshotID: string(manifest.ID), + } + buf := bytes.NewBuffer(nil) + enc := json.NewEncoder(buf) + if err = enc.Encode(meta); err != nil { + return fmt.Errorf("marshal meta json failed, meta: %+v, err: %w", meta, err) + } + err = s.underlying.Push(ctx, buf, rpath+metaSuffix) + if err != nil { + return err + } + + // remove the shadowed file from the kopia repo + if oldMetaFile != "" { + err := s.Remove(ctx, oldMetaFile, false) + if err != nil { + log(ctx).Warnf("unable to remove the shadowed file: %v", err) + return nil + } + } + + return nil +} + +func (s *kopiaStorage) Pull(ctx context.Context, rpath string, w io.Writer) error { + log(ctx).Infof("[KOPIA] Pull %s", rpath) + + rc, err := s.OpenFile(ctx, rpath, 0, -1) + if err != nil { + return fmt.Errorf("fail to OpenFile, %w", err) + } + defer rc.Close() + _, err = io.Copy(w, rc) + return err +} + +func (s *kopiaStorage) OpenFile(ctx context.Context, rpath string, offset, length int64) (io.ReadCloser, error) { + log(ctx).Infof("[KOPIA] OpenFile %s", rpath) + meta, err := s.loadMeta(ctx, rpath) + if err != nil { + return nil, err + } + rc, err := dumpSingleFile(ctx, s.rep, meta.SnapshotID, meta.Name, offset, length) + if err != nil { + return nil, fmt.Errorf("dumpSingleFile error: %w", err) + } + return rc, err +} + +func (s *kopiaStorage) loadMeta(ctx context.Context, rpath string) (*meta, error) { + buf := bytes.NewBuffer(nil) + err := s.underlying.Pull(ctx, rpath+metaSuffix, buf) + if err != nil { + return nil, fmt.Errorf("fail to pull underlying %q, %w", rpath+metaSuffix, err) + } + meta := &meta{} + err = json.Unmarshal(buf.Bytes(), meta) + if err != nil { + return nil, fmt.Errorf("unmarshal meta json failed, err: %w", err) + } + return meta, nil +} + +func (s *kopiaStorage) Remove(ctx context.Context, rpath string, recursive bool) error { + log(ctx).Infof("[KOPIA] Remove %s, recursive: %v", rpath, recursive) + + if !recursive { + meta, err := s.loadMeta(ctx, rpath) + if err != nil { + return err + } + err = repo.WriteSession(ctx, s.rep, repo.WriteSessionOptions{ + Purpose: "datasafed:remove", + }, func(ctx context.Context, w repo.RepositoryWriter) error { + return w.DeleteManifest(ctx, manifest.ID(meta.SnapshotID)) + }) + if err != nil { + return fmt.Errorf("fail to remove kopia snapshot %s, error: %w", meta.SnapshotID, err) + } + err = s.underlying.Remove(ctx, rpath+metaSuffix, false) + return util.WrappedErrOrNil(err, "fail to remove underlying %q", rpath+metaSuffix) + } + + dirStack := []string{rpath} + + // list the dir to remove files and record dirs in order + if _, err := s.underlying.List(ctx, rpath, &storage.ListOptions{ + Recursive: true, + Callback: func(en storage.DirEntry) error { + log(ctx).Debugf("listing %q, is dir: %v", en.Path(), en.IsDir()) + if en.IsDir() { + dirStack = append(dirStack, en.Path()) + return nil + } + // remove the current listed file + if strings.HasSuffix(en.Name(), metaSuffix) { + path := strings.TrimSuffix(en.Path(), metaSuffix) + err := s.Remove(ctx, path, false) + return util.WrappedErrOrNil(err, "fail to remove %q", path) + } else { + log(ctx).Warnf("listing non meta file %q", en.Path()) + return nil + } + }, + }); err != nil { + return err + } + + // remove dirs in the reversed order + for i := len(dirStack) - 1; i >= 0; i-- { + err := s.Rmdir(ctx, dirStack[i]) + if err != nil { + // ignore the error + log(ctx).Errorf("fail to rmdir %q, error: %v", dirStack[i], err) + } + } + return nil +} + +func (s *kopiaStorage) Rmdir(ctx context.Context, rpath string) error { + log(ctx).Infof("[KOPIA] Rmdir %s", rpath) + return s.underlying.Rmdir(ctx, rpath) +} + +func (s *kopiaStorage) Mkdir(ctx context.Context, rpath string) error { + log(ctx).Infof("[KOPIA] Mkdir %s", rpath) + return s.underlying.Mkdir(ctx, rpath) +} + +func (s *kopiaStorage) List(ctx context.Context, rpath string, opt *storage.ListOptions) ([]storage.DirEntry, error) { + log(ctx).Infof("[KOPIA] List %s, options: %+v", rpath, opt) + + var err error + if !strings.HasSuffix(rpath, "/") { + meta, err := s.loadMeta(ctx, rpath) + if err == nil { + en := storage.NewStaticDirEntry(false, filepath.Base(rpath), rpath, meta.Size, meta.ModTime) + if opt.Callback != nil { + err := opt.Callback(en) + if err != nil { + return nil, fmt.Errorf("interrupted by error: %w", err) + } + return nil, nil + } + return []storage.DirEntry{en}, nil + } + } + + if opt.PathIsFile { + if strings.HasSuffix(rpath, "/") { + return nil, storage.ErrIsDir + } + return nil, err + } + + callback := opt.Callback + var result []storage.DirEntry + cloneOpt := *opt + cloneOpt.Callback = func(en storage.DirEntry) error { + if !en.IsDir() { + if strings.HasSuffix(en.Name(), metaSuffix) { + filePath := strings.TrimSuffix(en.Path(), metaSuffix) + meta, err := s.loadMeta(ctx, filePath) + if err != nil { + return fmt.Errorf("load meta for file %q failed: %w", filePath, err) + } + fileName := strings.TrimSuffix(en.Name(), metaSuffix) + en = storage.NewStaticDirEntry(false, fileName, filePath, meta.Size, meta.ModTime) + } else { + log(ctx).Warnf("listing non meta file %s", en.Path()) + return nil + } + } + if callback != nil { + return callback(en) + } else { + result = append(result, en) + return nil + } + } + _, err = s.underlying.List(ctx, rpath, &cloneOpt) + return result, err +} + +func (s *kopiaStorage) Stat(ctx context.Context, rpath string) (storage.StatResult, error) { + log(ctx).Infof("[KOPIA] Stat %s", rpath) + + meta, err := s.loadMeta(ctx, rpath) + if err == nil { + return storage.StatResult{ + TotalSize: meta.Size, + Entries: 1, + Files: 1, + }, nil + } + + var result storage.StatResult + opt := &storage.ListOptions{ + Recursive: true, + Callback: func(en storage.DirEntry) error { + if en.IsDir() { + result.Dirs++ + } else { + result.Files++ + result.TotalSize += en.Size() + } + return nil + }, + } + + _, err = s.List(ctx, rpath, opt) + result.Entries = result.Dirs + result.Files + return result, err +} diff --git a/pkg/storage/kopia/maintenance.go b/pkg/storage/kopia/maintenance.go new file mode 100644 index 0000000..112f998 --- /dev/null +++ b/pkg/storage/kopia/maintenance.go @@ -0,0 +1,64 @@ +package kopia + +import ( + "context" + "fmt" + + "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/repo/maintenance" + "github.com/kopia/kopia/snapshot/snapshotmaintenance" + + "github.com/apecloud/datasafed/pkg/storage" +) + +type unwrap interface { + Unwrap() storage.Storage +} + +func asKopiaStorage(st storage.Storage) (*kopiaStorage, bool) { + for { + if ks, ok := st.(*kopiaStorage); ok { + return ks, true + } + if u, ok := st.(unwrap); ok { + st = u.Unwrap() + continue + } + return nil, false + } +} + +func RunMaintenance(ctx context.Context, st storage.Storage, safety string) error { + ks, ok := asKopiaStorage(st) + if !ok { + return fmt.Errorf("requires *kopiaStorage, got %T", st) + } + + directRep, ok := ks.rep.(repo.DirectRepository) + if !ok { + return fmt.Errorf("requires repo.DirectRepository, got %T", ks.rep) + } + + return repo.DirectWriteSession(ctx, directRep, repo.WriteSessionOptions{ + Purpose: "datasafed:maintenance", + }, func(ctx context.Context, dw repo.DirectRepositoryWriter) error { + mode := maintenance.ModeQuick + _, supportsEpochManager, err := dw.ContentManager().EpochManager() + if err != nil { + return fmt.Errorf("EpochManager error: %w", err) + } + if supportsEpochManager { + mode = maintenance.ModeFull + } + safetyParams := maintenance.SafetyFull + if safety == "none" { + safetyParams = maintenance.SafetyNone + } else { + safety = "full" // default full + } + log(ctx).Infof("[KOPIA] maintenance mode: %s, safety: %s", mode, safety) + // set force to true to ignore the ownership checking + force := true + return snapshotmaintenance.Run(ctx, dw, mode, force, safetyParams) + }) +} diff --git a/pkg/storage/kopia/progress.go b/pkg/storage/kopia/progress.go new file mode 100644 index 0000000..92fa961 --- /dev/null +++ b/pkg/storage/kopia/progress.go @@ -0,0 +1,88 @@ +package kopia + +import ( + "github.com/kopia/kopia/snapshot/snapshotfs" + + "github.com/apecloud/datasafed/pkg/logging" +) + +// LoggingUploadProgress is an implementation of UploadProgress. +type LoggingUploadProgress struct { + logger logging.Logger +} + +// UploadStarted implements UploadProgress. +func (p *LoggingUploadProgress) UploadStarted() { + p.logger.Infof("[PROGRESS] Upload started") +} + +// EstimatedDataSize implements UploadProgress. +func (p *LoggingUploadProgress) EstimatedDataSize(fileCount int, totalBytes int64) { + p.logger.Infof("[PROGRESS] Estimated data size: %d files, %d bytes", fileCount, totalBytes) +} + +// UploadFinished implements UploadProgress. +func (p *LoggingUploadProgress) UploadFinished() { + p.logger.Infof("[PROGRESS] Upload finished") +} + +// HashedBytes implements UploadProgress. +func (p *LoggingUploadProgress) HashedBytes(numBytes int64) { + p.logger.Infof("[PROGRESS] Hashed %d bytes", numBytes) +} + +// ExcludedFile implements UploadProgress. +func (p *LoggingUploadProgress) ExcludedFile(fname string, numBytes int64) { + p.logger.Infof("[PROGRESS] Excluded %s (%d bytes)", fname, numBytes) +} + +// ExcludedDir implements UploadProgress. +func (p *LoggingUploadProgress) ExcludedDir(dirname string) { + p.logger.Infof("[PROGRESS] Excluded %s", dirname) +} + +// CachedFile implements UploadProgress. +func (p *LoggingUploadProgress) CachedFile(fname string, numBytes int64) { + p.logger.Infof("[PROGRESS] Cached %s (%d bytes)", fname, numBytes) +} + +// UploadedBytes implements UploadProgress. +func (p *LoggingUploadProgress) UploadedBytes(numBytes int64) { + p.logger.Infof("[PROGRESS] Uploaded %d bytes", numBytes) +} + +// HashingFile implements UploadProgress. +func (p *LoggingUploadProgress) HashingFile(fname string) { + p.logger.Infof("[PROGRESS] Hashing %s", fname) +} + +// FinishedHashingFile implements UploadProgress. +func (p *LoggingUploadProgress) FinishedHashingFile(fname string, numBytes int64) { + p.logger.Infof("[PROGRESS] Finished hashing %s (%d bytes)", fname, numBytes) +} + +// FinishedFile implements UploadProgress. +func (p *LoggingUploadProgress) FinishedFile(fname string, err error) { + p.logger.Infof("[PROGRESS] Finished %s (%v)", fname, err) +} + +// StartedDirectory implements UploadProgress. +func (p *LoggingUploadProgress) StartedDirectory(dirname string) { + p.logger.Infof("[PROGRESS] Started %s", dirname) +} + +// FinishedDirectory implements UploadProgress. +func (p *LoggingUploadProgress) FinishedDirectory(dirname string) { + p.logger.Infof("[PROGRESS] Finished %s", dirname) +} + +// Error implements UploadProgress. +func (p *LoggingUploadProgress) Error(path string, err error, isIgnored bool) { + p.logger.Infof("[PROGRESS] Error %s (%v, ignored=%v)", path, err, isIgnored) +} + +var _ snapshotfs.UploadProgress = (*LoggingUploadProgress)(nil) + +func newLoggingUploadProgress(logger logging.Logger) *LoggingUploadProgress { + return &LoggingUploadProgress{logger: logger} +} diff --git a/pkg/storage/kopia/repo.go b/pkg/storage/kopia/repo.go new file mode 100644 index 0000000..1e68803 --- /dev/null +++ b/pkg/storage/kopia/repo.go @@ -0,0 +1,188 @@ +package kopia + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/repo/blob" + "github.com/kopia/kopia/repo/ecc" + "github.com/kopia/kopia/repo/encryption" + "github.com/kopia/kopia/repo/format" + "github.com/kopia/kopia/repo/hashing" + "github.com/kopia/kopia/repo/maintenance" + "github.com/kopia/kopia/snapshot/policy" +) + +const ( + formatVersion = format.FormatVersion3 + blockHashAlgorithm = hashing.DefaultAlgorithm + encryptionAlgorithm = encryption.DefaultAlgorithm + eccAlgorithm = ecc.DefaultAlgorithm + splitterAlgorithm = "DYNAMIC-512K-BUZHASH" // the granularity of the default splitter is too rough + compressionAlgorithm = "zstd" + defaultPassword = "d@ta$aFed" +) + +func getInitedRepository(ctx context.Context, rootPath, password, cacheID string) (repo.Repository, error) { + opts := &blobOptions{RootPath: rootPath} + configFile, err := writeTempConfigFile(opts, cacheID) + if err != nil { + return nil, err + } + if password == "" { + password = defaultPassword + } + rep, err := repo.Open(ctx, configFile, password, &repo.Options{}) + if errors.Is(err, blob.ErrBlobNotFound) { + st, err := newBlobStorage(ctx, opts, true) + if err != nil { + return nil, err + } + return initRepository(ctx, st, configFile, password) + } + if err != nil { + return nil, err + } + return rep, nil +} + +func writeTempConfigFile(opt *blobOptions, cacheID string) (string, error) { + // create temporary config file + f, err := os.CreateTemp("", "config*.json") + if err != nil { + return "", fmt.Errorf("create temporary config file failed, err: %w", err) + } + defer f.Close() + // Doc: https://kopia.io/docs/reference/command-line/#configuration-file + conf := map[string]interface{}{ + "storage": map[string]interface{}{ + "type": storageType, + "config": opt, + }, + } + if cacheID != "" { + cacheDir := filepath.Join(os.TempDir(), "kopiacache", cacheID) + conf["caching"] = map[string]interface{}{ + "cacheDirectory": cacheDir, + "maxCacheSize": 128 * 1024 * 1024, + "maxMetadataCacheSize": 32 * 1024 * 1024, + "maxListCacheDuration": 60, + } + } + + err = json.NewEncoder(f).Encode(conf) + if err != nil { + return "", fmt.Errorf("encode config file failed, err: %w", err) + } + return f.Name(), nil +} + +func ensureEmpty(ctx context.Context, s blob.Storage) error { + hasDataError := errors.New("has data") + + err := s.ListBlobs(ctx, "", func(cb blob.Metadata) error { + return hasDataError + }) + + if errors.Is(err, hasDataError) { + return errors.New("found existing data in storage location") + } + + if err != nil { + return fmt.Errorf("listing blobs error: %s", err) + } + return nil +} + +func initRepository(ctx context.Context, st blob.Storage, configFile string, password string) (repo.Repository, error) { + err := ensureEmpty(ctx, st) + if err != nil { + return nil, fmt.Errorf("initRepostory: ensureEmpty failed, %w", err) + } + + options := &repo.NewRepositoryOptions{ + BlockFormat: format.ContentFormat{ + MutableParameters: format.MutableParameters{ + Version: formatVersion, + }, + Hash: blockHashAlgorithm, + Encryption: encryptionAlgorithm, + ECC: eccAlgorithm, + ECCOverheadPercent: 0, // disable ECC + }, + + ObjectFormat: format.ObjectFormat{ + Splitter: splitterAlgorithm, + }, + + // no retention + RetentionMode: "", + RetentionPeriod: 0, + } + + if err := repo.Initialize(ctx, st, options, password); err != nil { + return nil, fmt.Errorf("initRepostory: cannot initialize repository, %w", err) + } + + rep, err := repo.Open(ctx, configFile, password, &repo.Options{}) + if err != nil { + return nil, fmt.Errorf("unable to open repository, %w", err) + } + + if err := populateRepository(ctx, rep); err != nil { + return nil, fmt.Errorf("error populating repository, %w", err) + } + return rep, nil +} + +func populateRepository(ctx context.Context, rep repo.Repository) error { + err := repo.WriteSession(ctx, rep, repo.WriteSessionOptions{ + Purpose: "populate repository", + }, func(ctx context.Context, w repo.RepositoryWriter) error { + myPolicy := *policy.DefaultPolicy + myPolicy.CompressionPolicy = policy.CompressionPolicy{ + CompressorName: compressionAlgorithm, + } + if err := policy.SetPolicy(ctx, w, policy.GlobalPolicySourceInfo, &myPolicy); err != nil { + return fmt.Errorf("unable to set global policy, %w", err) + } + + if err := setDefaultMaintenanceParameters(ctx, w); err != nil { + return fmt.Errorf("unable to set maintenance parameters, %w", err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("unable to write session, %w", err) + } + return nil +} + +func setDefaultMaintenanceParameters(ctx context.Context, rep repo.RepositoryWriter) error { + p := maintenance.DefaultParams() + p.Owner = rep.ClientOptions().UsernameAtHost() + + if dw, ok := rep.(repo.DirectRepositoryWriter); ok { + _, ok, err := dw.ContentReader().EpochManager() + if err != nil { + return fmt.Errorf("epoch manager, %w", err) + } + + if ok { + // disable quick maintenance cycle + p.QuickCycle.Enabled = false + } + } + + if err := maintenance.SetParams(ctx, rep, &p); err != nil { + return fmt.Errorf("unable to set maintenance params, %w", err) + } + + return nil +} diff --git a/pkg/storage/kopia/snapshot.go b/pkg/storage/kopia/snapshot.go new file mode 100644 index 0000000..a6e0bee --- /dev/null +++ b/pkg/storage/kopia/snapshot.go @@ -0,0 +1,196 @@ +package kopia + +import ( + "context" + "fmt" + "io" + "os" + "os/signal" + "time" + + "github.com/kopia/kopia/fs" + "github.com/kopia/kopia/fs/virtualfs" + "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/snapshot" + "github.com/kopia/kopia/snapshot/policy" + "github.com/kopia/kopia/snapshot/snapshotfs" +) + +func snapshotSingleFile(ctx context.Context, fileName string, r io.Reader, rep repo.RepositoryWriter, tags map[string]string) (*snapshot.Manifest, error) { + sourceInfo := snapshot.SourceInfo{ + Path: "-", + Host: rep.ClientOptions().Hostname, + UserName: rep.ClientOptions().Username, + } + fsEntry := virtualfs.NewStaticDirectory("-", []fs.Entry{ + virtualfs.StreamingFileFromReader(fileName, io.NopCloser(r)), + }) + setManual := true + + log(ctx).Infof("Snapshotting %v ...", sourceInfo) + + previous, err := findPreviousSnapshotManifest(ctx, rep, sourceInfo, nil) + if err != nil { + return nil, err + } + + policyTree, err := policy.TreeForSource(ctx, rep, sourceInfo) + if err != nil { + return nil, fmt.Errorf("unable to get policy tree, %w", err) + } + + u := setupUploader(ctx, rep) + manifest, err := u.Upload(ctx, fsEntry, policyTree, sourceInfo, previous...) + if err != nil { + // fail-fast uploads will fail here without recording a manifest, other uploads will + // possibly fail later. + return nil, fmt.Errorf("upload error, %w", err) + } + + manifest.Tags = tags + + if _, err = snapshot.SaveSnapshot(ctx, rep, manifest); err != nil { + return nil, fmt.Errorf("cannot save manifest, %w", err) + } + + if _, err = policy.ApplyRetentionPolicy(ctx, rep, sourceInfo, true); err != nil { + return nil, fmt.Errorf("unable to apply retention policy, %w", err) + } + + if setManual { + if err = policy.SetManual(ctx, rep, sourceInfo); err != nil { + return nil, fmt.Errorf("unable to set manual field in scheduling policy for source, %w", err) + } + } + + if ferr := rep.Flush(ctx); ferr != nil { + return nil, fmt.Errorf("flush error, %w", ferr) + } + + return manifest, reportSnapshotStatus(ctx, manifest) +} + +func reportSnapshotStatus(ctx context.Context, manifest *snapshot.Manifest) error { + var maybePartial string + if manifest.IncompleteReason != "" { + maybePartial = " partial" + } + + sourceInfo := manifest.Source + + snapID := manifest.ID + + log(ctx).Infof("Created%v snapshot with root %v and ID %v in %v", maybePartial, manifest.RootObjectID(), snapID, manifest.EndTime.Sub(manifest.StartTime).Truncate(time.Second)) + + if ds := manifest.RootEntry.DirSummary; ds != nil { + if ds.IgnoredErrorCount > 0 { + log(ctx).Warnf("Ignored %v error(s) while snapshotting %v.", ds.IgnoredErrorCount, sourceInfo) + } + + if ds.FatalErrorCount > 0 { + return fmt.Errorf("Found %v fatal error(s) while snapshotting %v.", ds.FatalErrorCount, sourceInfo) //nolint:revive + } + } + + return nil +} + +// findPreviousSnapshotManifest returns the list of previous snapshots for a given source, including +// last complete snapshot and possibly some number of incomplete snapshots following it. +func findPreviousSnapshotManifest(ctx context.Context, rep repo.Repository, sourceInfo snapshot.SourceInfo, noLaterThan *fs.UTCTimestamp) ([]*snapshot.Manifest, error) { + man, err := snapshot.ListSnapshots(ctx, rep, sourceInfo) + if err != nil { + return nil, fmt.Errorf("error listing previous snapshots, %w", err) + } + + // phase 1 - find latest complete snapshot. + var previousComplete *snapshot.Manifest + + var previousCompleteStartTime fs.UTCTimestamp + + var result []*snapshot.Manifest + + for _, p := range man { + if noLaterThan != nil && p.StartTime.After(*noLaterThan) { + continue + } + + if p.IncompleteReason == "" && (previousComplete == nil || p.StartTime.After(previousComplete.StartTime)) { + previousComplete = p + previousCompleteStartTime = p.StartTime + } + } + + if previousComplete != nil { + result = append(result, previousComplete) + } + + // add all incomplete snapshots after that + for _, p := range man { + if noLaterThan != nil && p.StartTime.After(*noLaterThan) { + continue + } + + if p.IncompleteReason != "" && p.StartTime.After(previousCompleteStartTime) { + result = append(result, p) + } + } + + return result, nil +} + +func setupUploader(ctx context.Context, rep repo.RepositoryWriter) *snapshotfs.Uploader { + u := snapshotfs.NewUploader(rep) + + onCtrlC(u.Cancel) + + u.ForceHashPercentage = 0 + u.ParallelUploads = 0 + + u.FailFast = true + u.Progress = newLoggingUploadProgress(log(ctx)) + + return u +} + +func onCtrlC(f func()) { + s := make(chan os.Signal, 1) + signal.Notify(s, os.Interrupt) + + go func() { + // invoke the function when Ctrl-C signal is delivered + <-s + f() + }() +} + +func dumpSingleFile(ctx context.Context, rep repo.Repository, snapshotID string, fileName string, offset, length int64) (io.ReadCloser, error) { + rootEntry, err := snapshotfs.FilesystemEntryFromIDWithPath(ctx, rep, snapshotID+"/"+fileName, true) + if err != nil { + return nil, err + } + file, ok := rootEntry.(fs.File) + if !ok { + return nil, fmt.Errorf("unexpected type %T, expected fs.File", rootEntry) + } + reader, err := file.Open(ctx) + if err != nil { + return nil, fmt.Errorf("unable to open fs.File, %w", err) + } + if offset > 0 { + _, err := reader.Seek(offset, io.SeekStart) + if err != nil { + return nil, fmt.Errorf("unable to seek, offset: %d, err: %w", offset, err) + } + } + if length >= 0 { + return &struct { + io.Reader + io.Closer + }{ + Reader: io.LimitReader(reader, length), + Closer: reader, + }, nil + } + return reader, nil +} diff --git a/pkg/storage/kopia/underlying.go b/pkg/storage/kopia/underlying.go new file mode 100644 index 0000000..736c484 --- /dev/null +++ b/pkg/storage/kopia/underlying.go @@ -0,0 +1,15 @@ +package kopia + +import "github.com/apecloud/datasafed/pkg/storage" + +var ( + underlying storage.Storage +) + +func SetUnderlyingStorage(st storage.Storage) { + underlying = st +} + +func GetUnderlyingStorage() storage.Storage { + return underlying +} diff --git a/pkg/storage/rclone/rclone.go b/pkg/storage/rclone/rclone.go index 2a7afbe..8d3800b 100644 --- a/pkg/storage/rclone/rclone.go +++ b/pkg/storage/rclone/rclone.go @@ -16,7 +16,9 @@ import ( "github.com/rclone/rclone/fs/config" "github.com/rclone/rclone/fs/operations" + "github.com/apecloud/datasafed/pkg/logging" "github.com/apecloud/datasafed/pkg/storage" + "github.com/apecloud/datasafed/pkg/storage/sanitized" ) const ( @@ -24,25 +26,28 @@ const ( rootKey = "root" ) +var log = logging.Module("storage/rclone") + type rcloneStorage struct { f fs.Fs } var _ storage.Storage = (*rcloneStorage)(nil) -func New(cfg map[string]string) (storage.Storage, error) { +func New(ctx context.Context, cfg map[string]string, basePath string) (storage.Storage, error) { rcloneCfg := config.Data() for k, v := range cfg { rcloneCfg.SetValue(remoteName, k, v) } root := cfg[rootKey] - f, err := fs.NewFs(context.Background(), remoteName+":"+root) + f, err := fs.NewFs(ctx, remoteName+":"+root) if err != nil { return nil, err } - return &rcloneStorage{ + s := &rcloneStorage{ f: f, - }, nil + } + return sanitized.New(ctx, basePath, s) } func (s *rcloneStorage) Push(ctx context.Context, r io.Reader, rpath string) error { @@ -73,6 +78,9 @@ func (s *rcloneStorage) Pull(ctx context.Context, rpath string, w io.Writer) err rpath = normalizeRemotePath(rpath) obj, err := s.f.NewObject(ctx, rpath) if err != nil { + if errors.Is(err, fs.ErrorObjectNotFound) { + return storage.ErrObjectNotFound + } return err } rc, err := obj.Open(ctx) @@ -84,13 +92,23 @@ func (s *rcloneStorage) Pull(ctx context.Context, rpath string, w io.Writer) err return err } -func (s *rcloneStorage) ReadObject(ctx context.Context, rpath string) (io.ReadCloser, error) { +func (s *rcloneStorage) OpenFile(ctx context.Context, rpath string, offset, length int64) (io.ReadCloser, error) { rpath = normalizeRemotePath(rpath) obj, err := s.f.NewObject(ctx, rpath) if err != nil { + if errors.Is(err, fs.ErrorObjectNotFound) { + return nil, storage.ErrObjectNotFound + } return nil, err } - return obj.Open(ctx) + rangeOpt := fs.RangeOption{Start: 0, End: -1} + if offset > 0 { + rangeOpt.Start = offset + } + if length > 0 { + rangeOpt.End = offset + (length - 1) + } + return obj.Open(ctx, &rangeOpt) } func (s *rcloneStorage) Remove(ctx context.Context, rpath string, recursive bool) error { @@ -111,7 +129,11 @@ func (s *rcloneStorage) Remove(ctx context.Context, rpath string, recursive bool func (s *rcloneStorage) Rmdir(ctx context.Context, rpath string) error { rpath = normalizeRemotePath(rpath) - return s.f.Rmdir(ctx, rpath) + err := s.f.Rmdir(ctx, rpath) + if errors.Is(err, fs.ErrorDirNotFound) || os.IsNotExist(err) { + return nil + } + return err } func (s *rcloneStorage) Mkdir(ctx context.Context, rpath string) error { @@ -131,42 +153,81 @@ func (s *rcloneStorage) list(ctx context.Context, rpath string, opt *storage.Lis ci.MaxDepth = opt.MaxDepth } } + rpath = strings.TrimSuffix(rpath, "/") return operations.ListJSON(ctx, s.f, rpath, ljOpt, callback) } func (s *rcloneStorage) List(ctx context.Context, rpath string, opt *storage.ListOptions) ([]storage.DirEntry, error) { + log(ctx).Infof("[RCLONE] List %s, options %+v", rpath, opt) rpath = normalizeRemotePath(rpath) - obj, err := s.f.NewObject(ctx, rpath) - if err == nil { - entry := storage.NewStaticDirEntry(false, filepath.Base(obj.Remote()), - obj.Remote(), obj.Size(), obj.ModTime(ctx)) - return []storage.DirEntry{entry}, nil + + // set UseServerModTime to true to reduce the number of API calls + var ci *fs.ConfigInfo + ctx, ci = fs.AddConfig(ctx) + ci.UseServerModTime = true + + var err error + if !strings.HasSuffix(rpath, "/") { + var obj fs.Object + obj, err = s.f.NewObject(ctx, rpath) + if err == nil { + entry := storage.NewStaticDirEntry(false, filepath.Base(obj.Remote()), + obj.Remote(), obj.Size(), obj.ModTime(ctx)) + if opt.Callback != nil { + err = opt.Callback(entry) + if err != nil { + return nil, fmt.Errorf("interrupted by error: %w", err) + } + return nil, nil + } + return []storage.DirEntry{entry}, nil + } } + + if opt.PathIsFile { + if errors.Is(err, fs.ErrorObjectNotFound) { + return nil, storage.ErrObjectNotFound + } + if strings.HasSuffix(rpath, "/") { + return nil, storage.ErrIsDir + } + return nil, err + } + var result []storage.DirEntry err = s.list(ctx, rpath, opt, func(item *operations.ListJSONItem) error { en := storage.NewStaticDirEntry(item.IsDir, item.Name, item.Path, item.Size, item.ModTime.When) - result = append(result, en) + if opt.Callback != nil { + return opt.Callback(en) + } else { + result = append(result, en) + } return nil }) + if errors.Is(err, fs.ErrorDirNotFound) { + return nil, storage.ErrDirNotFound + } return result, err } func (s *rcloneStorage) Stat(ctx context.Context, rpath string) (storage.StatResult, error) { rpath = normalizeRemotePath(rpath) - obj, err := s.f.NewObject(ctx, rpath) - if err == nil { - return storage.StatResult{ - TotalSize: obj.Size(), - Entries: 1, - Files: 1, - }, nil + if !strings.HasSuffix(rpath, "/") { + obj, err := s.f.NewObject(ctx, rpath) + if err == nil { + return storage.StatResult{ + TotalSize: obj.Size(), + Entries: 1, + Files: 1, + }, nil + } } opt := &storage.ListOptions{ Recursive: true, } var result storage.StatResult - err = s.list(ctx, rpath, opt, func(item *operations.ListJSONItem) error { + err := s.list(ctx, rpath, opt, func(item *operations.ListJSONItem) error { if item.IsBucket { // ignore buckets return nil @@ -184,12 +245,9 @@ func (s *rcloneStorage) Stat(ctx context.Context, rpath string) (storage.StatRes } func normalizeRemotePath(rpath string) string { - rpath = filepath.Clean(rpath) - rpath = strings.TrimPrefix(rpath, "./") - rpath = strings.TrimPrefix(rpath, "/") if rpath == "." { // rclone doesn't accept "." as a remote path - return "" + return "/" } return rpath } diff --git a/pkg/storage/sanitized/sanitized.go b/pkg/storage/sanitized/sanitized.go new file mode 100644 index 0000000..c4e8b50 --- /dev/null +++ b/pkg/storage/sanitized/sanitized.go @@ -0,0 +1,171 @@ +package sanitized + +import ( + "context" + "fmt" + "io" + "path/filepath" + "strings" + + "github.com/apecloud/datasafed/pkg/logging" + "github.com/apecloud/datasafed/pkg/storage" +) + +var log = logging.Module("storage/sanitized") + +type sanitizedStorage struct { + basePath string + underlying storage.Storage +} + +func New(ctx context.Context, basePath string, underlying storage.Storage) (storage.Storage, error) { + basePath, err := verifiedBasePath(basePath) + if err != nil { + return nil, err + } + return &sanitizedStorage{ + basePath: basePath, + underlying: underlying, + }, nil +} + +func verifiedBasePath(basePath string) (string, error) { + if basePath == "" { + return "", nil + } + basePath = filepath.Clean(basePath) + if strings.HasPrefix(basePath, "..") { + return "", fmt.Errorf("base path %q prefixed with '..'", basePath) + } + if basePath == "." { + basePath = "" + } else { + basePath = strings.TrimPrefix(basePath, "/") + basePath = strings.TrimPrefix(basePath, "./") + } + return basePath, nil +} + +func (s *sanitizedStorage) relocate(rpath string) (string, error) { + rpath = filepath.Clean(rpath) + rpath = strings.TrimPrefix(rpath, "./") + rpath = strings.TrimPrefix(rpath, "/") + if strings.HasPrefix(rpath, "..") { + return "", fmt.Errorf("prefixed with '..'") + } + if s.basePath == "" { + return rpath, nil + } + return filepath.Join(s.basePath, rpath), nil +} + +func (s *sanitizedStorage) Push(ctx context.Context, r io.Reader, rpath string) error { + if strings.HasSuffix(rpath, "/") { + return fmt.Errorf("rpath %q ends with '/'", rpath) + } + rpath, err := s.relocate(rpath) + if err != nil { + return fmt.Errorf("invalid rpath %q: %w", rpath, err) + } + return s.underlying.Push(ctx, r, rpath) +} + +func (s *sanitizedStorage) Pull(ctx context.Context, rpath string, w io.Writer) error { + if strings.HasSuffix(rpath, "/") { + return fmt.Errorf("rpath %q ends with '/'", rpath) + } + rpath, err := s.relocate(rpath) + if err != nil { + return fmt.Errorf("invalid rpath %q: %w", rpath, err) + } + return s.underlying.Pull(ctx, rpath, w) +} + +func (s *sanitizedStorage) Remove(ctx context.Context, rpath string, recursive bool) error { + rpath, err := s.relocate(rpath) + if err != nil { + return fmt.Errorf("invalid rpath %q: %w", rpath, err) + } + return s.underlying.Remove(ctx, rpath, recursive) +} + +func (s *sanitizedStorage) Rmdir(ctx context.Context, rpath string) error { + rpath, err := s.relocate(rpath) + if err != nil { + return fmt.Errorf("invalid rpath %q: %w", rpath, err) + } + return s.underlying.Rmdir(ctx, rpath) +} + +func (s *sanitizedStorage) Mkdir(ctx context.Context, rpath string) error { + rpath, err := s.relocate(rpath) + if err != nil { + return fmt.Errorf("invalid rpath %q: %w", rpath, err) + } + return s.underlying.Mkdir(ctx, rpath) +} + +func (s *sanitizedStorage) adjustPath(ctx context.Context, e storage.DirEntry) storage.DirEntry { + if s.basePath == "" { + return e + } + final, err := filepath.Rel(s.basePath, e.Path()) + if err != nil { + log(ctx).Warnf("[SANITIZED] failed to get relative path %q to %q: %v", e.Path(), s.basePath, err) + return e + } + return storage.NewStaticDirEntry(e.IsDir(), e.Name(), final, e.Size(), e.MTime()) +} + +func (s *sanitizedStorage) List(ctx context.Context, rpath string, opt *storage.ListOptions) ([]storage.DirEntry, error) { + if opt.PathIsFile && strings.HasSuffix(rpath, "/") { + return nil, fmt.Errorf("rpath %q ends with '/', but PathIsFile is true", rpath) + } + isDir := strings.HasSuffix(rpath, "/") + rpath, err := s.relocate(rpath) + if err != nil { + return nil, fmt.Errorf("invalid rpath %q: %w", rpath, err) + } + if isDir { + rpath += "/" + } + cloneOpt := *opt + if cloneOpt.Callback != nil { + original := cloneOpt.Callback + cloneOpt.Callback = func(e storage.DirEntry) error { + return original(s.adjustPath(ctx, e)) + } + } + entries, err := s.underlying.List(ctx, rpath, &cloneOpt) + for i := range entries { + entries[i] = s.adjustPath(ctx, entries[i]) + } + return entries, err +} + +func (s *sanitizedStorage) Stat(ctx context.Context, rpath string) (storage.StatResult, error) { + isDir := strings.HasSuffix(rpath, "/") + rpath, err := s.relocate(rpath) + if err != nil { + return storage.StatResult{}, fmt.Errorf("invalid rpath %q: %w", rpath, err) + } + if isDir { + rpath += "/" + } + return s.underlying.Stat(ctx, rpath) +} + +func (s *sanitizedStorage) OpenFile(ctx context.Context, rpath string, offset int64, length int64) (io.ReadCloser, error) { + if strings.HasSuffix(rpath, "/") { + return nil, fmt.Errorf("rpath %q ends with '/'", rpath) + } + rpath, err := s.relocate(rpath) + if err != nil { + return nil, fmt.Errorf("invalid rpath %q: %w", rpath, err) + } + return s.underlying.OpenFile(ctx, rpath, offset, length) +} + +func (s *sanitizedStorage) Unwrap() storage.Storage { + return s.underlying +} diff --git a/pkg/storage/types.go b/pkg/storage/types.go index e2165b5..c7ec1aa 100644 --- a/pkg/storage/types.go +++ b/pkg/storage/types.go @@ -2,10 +2,17 @@ package storage import ( "context" + "errors" "io" "time" ) +var ( + ErrObjectNotFound = errors.New("object not found") + ErrDirNotFound = errors.New("directory not found") + ErrIsDir = errors.New("path is a directory") +) + type DirEntry interface { IsDir() bool Name() string @@ -15,10 +22,12 @@ type DirEntry interface { } type ListOptions struct { - DirsOnly bool - FilesOnly bool - MaxDepth int - Recursive bool + DirsOnly bool + FilesOnly bool + MaxDepth int + Recursive bool + PathIsFile bool + Callback func(DirEntry) error } type StatResult struct { @@ -54,14 +63,21 @@ type Storage interface { // List lists the contents of the given path. // The `rpath` parameter can also be a file, in this case the function // will return a list with a single entry. + // It's recommended to add '/' to the end of `rpath` if it points to a directory. + // If a `Callback` is specified in the `ListOptions`, it will be called + // for each entry. In this case, the method returns an empty list. If the + // callback returns an error, the function will stop and return the error. List(ctx context.Context, rpath string, opt *ListOptions) ([]DirEntry, error) // Stat returns the information about the given path. // The `rpath` parameter can be a file. + // It's recommended to add '/' to the end of `rpath` if it points to a directory. Stat(ctx context.Context, rpath string) (StatResult, error) - // ReadObject reads a file. - ReadObject(ctx context.Context, rpath string) (io.ReadCloser, error) + // OpenFile returns a file reader for the given path. + // The `offset` and `length` parameters are used to specify the range + // of the file to read. If `length` is -1, the entire file will be read. + OpenFile(ctx context.Context, rpath string, offset int64, length int64) (io.ReadCloser, error) } type staticDirEntry struct { diff --git a/pkg/util/errors.go b/pkg/util/errors.go new file mode 100644 index 0000000..bb78efa --- /dev/null +++ b/pkg/util/errors.go @@ -0,0 +1,17 @@ +package util + +import ( + "fmt" + "strings" +) + +func WrappedErrOrNil(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + msg := fmt.Sprintf(format, args...) + if !strings.Contains(msg, "%w") { + msg = msg + ", error: %w" + } + return fmt.Errorf(msg, err) +}