From aa5e4cfb3326c999912afd5140740bf59e5058a4 Mon Sep 17 00:00:00 2001 From: Shikhar Mall Date: Tue, 1 Feb 2022 10:29:13 -0800 Subject: [PATCH] refactor(cli): An in-memory storage mock setup for CLI tests (#1697) * refactor cli tests to allow the use of in-memory mock * use in-memory repo for set-parameters cli tests * move inmemory storage provider into test package Co-authored-by: Shikhar Mall --- cli/app.go | 15 +++++- cli/command_repository_connect.go | 10 ++-- cli/command_repository_connect_from_config.go | 6 +-- cli/command_repository_create.go | 12 ++--- cli/command_repository_repair.go | 10 ++-- cli/command_repository_set_parameters_test.go | 33 +++++++++--- cli/command_repository_sync.go | 10 ++-- cli/storage_azure.go | 4 +- cli/storage_b2.go | 4 +- cli/storage_filesystem.go | 4 +- cli/storage_gcs.go | 4 +- cli/storage_inmemory_test.go | 29 +++++++++++ cli/storage_providers.go | 52 +++++++++++-------- cli/storage_rclone.go | 4 +- cli/storage_s3.go | 4 +- cli/storage_sftp.go | 4 +- cli/storage_webdav.go | 4 +- internal/repotesting/reconnectable_storage.go | 24 +++++---- internal/repotesting/repotesting.go | 2 +- 19 files changed, 153 insertions(+), 82 deletions(-) create mode 100644 cli/storage_inmemory_test.go diff --git a/cli/app.go b/cli/app.go index 679da7b1d..356da3d6b 100644 --- a/cli/app.go +++ b/cli/app.go @@ -89,7 +89,7 @@ type appServices interface { type advancedAppServices interface { appServices - storageProviderServices + StorageProviderServices runConnectCommandWithStorage(ctx context.Context, co *connectOptions, st blob.Storage) error runConnectCommandWithStorageAndPassword(ctx context.Context, co *connectOptions, st blob.Storage, password string) error @@ -121,6 +121,7 @@ type App struct { persistCredentials bool disableInternalLog bool AdvancedCommands string + cliStorageProviders []StorageProvider currentAction string onExitCallbacks []func() @@ -274,6 +275,18 @@ type commandParent interface { func NewApp() *App { return &App{ progress: &cliProgress{}, + cliStorageProviders: []StorageProvider{ + {"from-config", "the provided configuration file", func() StorageFlags { return &storageFromConfigFlags{} }}, + + {"azure", "an Azure blob storage", func() StorageFlags { return &storageAzureFlags{} }}, + {"b2", "a B2 bucket", func() StorageFlags { return &storageB2Flags{} }}, + {"filesystem", "a filesystem", func() StorageFlags { return &storageFilesystemFlags{} }}, + {"gcs", "a Google Cloud Storage bucket", func() StorageFlags { return &storageGCSFlags{} }}, + {"rclone", "a rclone-based provided", func() StorageFlags { return &storageRcloneFlags{} }}, + {"s3", "an S3 bucket", func() StorageFlags { return &storageS3Flags{} }}, + {"sftp", "an SFTP storage", func() StorageFlags { return &storageSFTPFlags{} }}, + {"webdav", "a WebDAV storage", func() StorageFlags { return &storageWebDAVFlags{} }}, + }, // testability hooks osExit: os.Exit, diff --git a/cli/command_repository_connect.go b/cli/command_repository_connect.go index d175f0684..6189a2df1 100644 --- a/cli/command_repository_connect.go +++ b/cli/command_repository_connect.go @@ -25,14 +25,14 @@ func (c *commandRepositoryConnect) setup(svc advancedAppServices, parent command c.co.setup(cmd) c.server.setup(svc, cmd, &c.co) - for _, prov := range cliStorageProviders() { + for _, prov := range svc.storageProviders() { // Set up 'connect' subcommand - f := prov.newFlags() - cc := cmd.Command(prov.name, "Connect to repository in "+prov.description) - f.setup(svc, cc) + f := prov.NewFlags() + cc := cmd.Command(prov.Name, "Connect to repository in "+prov.Description) + f.Setup(svc, cc) cc.Action(func(_ *kingpin.ParseContext) error { ctx := svc.rootContext() - st, err := f.connect(ctx, false, 0) + st, err := f.Connect(ctx, false, 0) if err != nil { return errors.Wrap(err, "can't connect to storage") } diff --git a/cli/command_repository_connect_from_config.go b/cli/command_repository_connect_from_config.go index f413e4d15..1c83ea317 100644 --- a/cli/command_repository_connect_from_config.go +++ b/cli/command_repository_connect_from_config.go @@ -14,17 +14,17 @@ type storageFromConfigFlags struct { connectFromConfigFile string connectFromConfigToken string - sps storageProviderServices + sps StorageProviderServices } -func (c *storageFromConfigFlags) setup(sps storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageFromConfigFlags) Setup(sps StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("file", "Path to the configuration file").StringVar(&c.connectFromConfigFile) cmd.Flag("token", "Configuration token").StringVar(&c.connectFromConfigToken) c.sps = sps } -func (c *storageFromConfigFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageFromConfigFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { if isCreate { return nil, errors.New("not supported") } diff --git a/cli/command_repository_create.go b/cli/command_repository_create.go index a2aa87c6f..e9cfa6a1b 100644 --- a/cli/command_repository_create.go +++ b/cli/command_repository_create.go @@ -52,18 +52,18 @@ func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandP c.svc = svc c.out.setup(svc) - for _, prov := range cliStorageProviders() { - if prov.name == "from-config" { + for _, prov := range svc.storageProviders() { + if prov.Name == "from-config" { continue } // Set up 'create' subcommand - f := prov.newFlags() - cc := cmd.Command(prov.name, "Create repository in "+prov.description) - f.setup(svc, cc) + f := prov.NewFlags() + cc := cmd.Command(prov.Name, "Create repository in "+prov.Description) + f.Setup(svc, cc) cc.Action(func(_ *kingpin.ParseContext) error { ctx := svc.rootContext() - st, err := f.connect(ctx, true, c.createFormatVersion) + st, err := f.Connect(ctx, true, c.createFormatVersion) if err != nil { return errors.Wrap(err, "can't connect to storage") } diff --git a/cli/command_repository_repair.go b/cli/command_repository_repair.go index 11affd204..2367d932b 100644 --- a/cli/command_repository_repair.go +++ b/cli/command_repository_repair.go @@ -25,13 +25,13 @@ func (c *commandRepositoryRepair) setup(svc advancedAppServices, parent commandP cmd.Flag("recover-format-block-prefixes", "Prefixes of file names").StringsVar(&c.repairCommandRecoverFormatBlobPrefixes) cmd.Flag("dry-run", "Do not modify repository").Short('n').BoolVar(&c.repairDryRun) - for _, prov := range cliStorageProviders() { - f := prov.newFlags() - cc := cmd.Command(prov.name, "Repair repository in "+prov.description) - f.setup(svc, cc) + for _, prov := range svc.storageProviders() { + f := prov.NewFlags() + cc := cmd.Command(prov.Name, "Repair repository in "+prov.Description) + f.Setup(svc, cc) cc.Action(func(_ *kingpin.ParseContext) error { ctx := svc.rootContext() - st, err := f.connect(ctx, false, 0) + st, err := f.Connect(ctx, false, 0) if err != nil { return errors.Wrap(err, "can't connect to storage") } diff --git a/cli/command_repository_set_parameters_test.go b/cli/command_repository_set_parameters_test.go index b10eb57de..830ea3d99 100644 --- a/cli/command_repository_set_parameters_test.go +++ b/cli/command_repository_set_parameters_test.go @@ -3,16 +3,39 @@ import ( "testing" + "github.com/alecthomas/kingpin" "github.com/stretchr/testify/require" + "github.com/kopia/kopia/cli" + "github.com/kopia/kopia/internal/blobtesting" + "github.com/kopia/kopia/internal/repotesting" "github.com/kopia/kopia/repo/content" "github.com/kopia/kopia/tests/testenv" ) -func (s *formatSpecificTestSuite) TestRepositorySetParameters(t *testing.T) { - env := testenv.NewCLITest(t, s.formatFlags, testenv.NewInProcRunner(t)) +func (s *formatSpecificTestSuite) setupInMemoryRepo(t *testing.T) *testenv.CLITest { + t.Helper() - env.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", env.RepoDir) + runner := testenv.NewInProcRunner(t) + runner.CustomizeApp = func(a *cli.App, kp *kingpin.Application) { + a.AddStorageProvider(cli.StorageProvider{ + Name: "in-memory", + Description: "in-memory storage backend", + NewFlags: func() cli.StorageFlags { return &storageInMemoryFlags{} }, + }) + } + + env := testenv.NewCLITest(t, s.formatFlags, runner) + st := repotesting.NewReconnectableStorage(t, blobtesting.NewVersionedMapStorage(nil)) + + env.RunAndExpectSuccess(t, "repo", "create", "in-memory", "--uuid", + st.ConnectionInfo().Config.(*repotesting.ReconnectableStorageOptions).UUID) + + return env +} + +func (s *formatSpecificTestSuite) TestRepositorySetParameters(t *testing.T) { + env := s.setupInMemoryRepo(t) out := env.RunAndExpectSuccess(t, "repository", "status") // default values @@ -40,9 +63,7 @@ func (s *formatSpecificTestSuite) TestRepositorySetParameters(t *testing.T) { } func (s *formatSpecificTestSuite) TestRepositorySetParametersUpgrade(t *testing.T) { - env := testenv.NewCLITest(t, s.formatFlags, testenv.NewInProcRunner(t)) - - env.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", env.RepoDir) + env := s.setupInMemoryRepo(t) out := env.RunAndExpectSuccess(t, "repository", "status") // default values diff --git a/cli/command_repository_sync.go b/cli/command_repository_sync.go index 0024cbe34..42b23ddcb 100644 --- a/cli/command_repository_sync.go +++ b/cli/command_repository_sync.go @@ -50,14 +50,14 @@ func (c *commandRepositorySyncTo) setup(svc advancedAppServices, parent commandP // needs to be 64-bit aligned on ARM c.nextSyncOutputTime = new(timetrack.Throttle) - for _, prov := range cliStorageProviders() { + for _, prov := range svc.storageProviders() { // Set up 'sync-to' subcommand - f := prov.newFlags() - cc := cmd.Command(prov.name, "Synchronize repository data to another repository in "+prov.description) - f.setup(svc, cc) + f := prov.NewFlags() + cc := cmd.Command(prov.Name, "Synchronize repository data to another repository in "+prov.Description) + f.Setup(svc, cc) cc.Action(func(_ *kingpin.ParseContext) error { ctx := svc.rootContext() - st, err := f.connect(ctx, false, 0) + st, err := f.Connect(ctx, false, 0) if err != nil { return errors.Wrap(err, "can't connect to storage") } diff --git a/cli/storage_azure.go b/cli/storage_azure.go index b66b8fdfd..6cf3a83bf 100644 --- a/cli/storage_azure.go +++ b/cli/storage_azure.go @@ -13,7 +13,7 @@ type storageAzureFlags struct { azOptions azure.Options } -func (c *storageAzureFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageAzureFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("container", "Name of the Azure blob container").Required().StringVar(&c.azOptions.Container) cmd.Flag("storage-account", "Azure storage account name (overrides AZURE_STORAGE_ACCOUNT environment variable)").Required().Envar("AZURE_STORAGE_ACCOUNT").StringVar(&c.azOptions.StorageAccount) cmd.Flag("storage-key", "Azure storage account key (overrides AZURE_STORAGE_KEY environment variable)").Envar("AZURE_STORAGE_KEY").StringVar(&c.azOptions.StorageKey) @@ -24,7 +24,7 @@ func (c *storageAzureFlags) setup(_ storageProviderServices, cmd *kingpin.CmdCla commonThrottlingFlags(cmd, &c.azOptions.Limits) } -func (c *storageAzureFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageAzureFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { // nolint:wrapcheck return azure.New(ctx, &c.azOptions) } diff --git a/cli/storage_b2.go b/cli/storage_b2.go index fe537e6dc..94a90be9a 100644 --- a/cli/storage_b2.go +++ b/cli/storage_b2.go @@ -13,7 +13,7 @@ type storageB2Flags struct { b2options b2.Options } -func (c *storageB2Flags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageB2Flags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("bucket", "Name of the B2 bucket").Required().StringVar(&c.b2options.BucketName) cmd.Flag("key-id", "Key ID (overrides B2_KEY_ID environment variable)").Required().Envar("B2_KEY_ID").StringVar(&c.b2options.KeyID) cmd.Flag("key", "Secret key (overrides B2_KEY environment variable)").Required().Envar("B2_KEY").StringVar(&c.b2options.Key) @@ -21,7 +21,7 @@ func (c *storageB2Flags) setup(_ storageProviderServices, cmd *kingpin.CmdClause commonThrottlingFlags(cmd, &c.b2options.Limits) } -func (c *storageB2Flags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageB2Flags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { // nolint:wrapcheck return b2.New(ctx, &c.b2options) } diff --git a/cli/storage_filesystem.go b/cli/storage_filesystem.go index c93101ad4..0f6d70aa6 100644 --- a/cli/storage_filesystem.go +++ b/cli/storage_filesystem.go @@ -28,7 +28,7 @@ type storageFilesystemFlags struct { connectFlat bool } -func (c *storageFilesystemFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageFilesystemFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("path", "Path to the repository").Required().StringVar(&c.options.Path) cmd.Flag("owner-uid", "User ID owning newly created files").PlaceHolder("USER").StringVar(&c.connectOwnerUID) cmd.Flag("owner-gid", "Group ID owning newly created files").PlaceHolder("GROUP").StringVar(&c.connectOwnerGID) @@ -40,7 +40,7 @@ func (c *storageFilesystemFlags) setup(_ storageProviderServices, cmd *kingpin.C commonThrottlingFlags(cmd, &c.options.Limits) } -func (c *storageFilesystemFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageFilesystemFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { fso := c.options fso.Path = ospath.ResolveUserFriendlyPath(fso.Path, false) diff --git a/cli/storage_gcs.go b/cli/storage_gcs.go index 441ecceb9..54f880524 100644 --- a/cli/storage_gcs.go +++ b/cli/storage_gcs.go @@ -18,7 +18,7 @@ type storageGCSFlags struct { embedCredentials bool } -func (c *storageGCSFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageGCSFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("bucket", "Name of the Google Cloud Storage bucket").Required().StringVar(&c.options.BucketName) cmd.Flag("prefix", "Prefix to use for objects in the bucket").StringVar(&c.options.Prefix) cmd.Flag("read-only", "Use read-only GCS scope to prevent write access").BoolVar(&c.options.ReadOnly) @@ -28,7 +28,7 @@ func (c *storageGCSFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClaus commonThrottlingFlags(cmd, &c.options.Limits) } -func (c *storageGCSFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageGCSFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { if c.embedCredentials { data, err := os.ReadFile(c.options.ServiceAccountCredentialsFile) if err != nil { diff --git a/cli/storage_inmemory_test.go b/cli/storage_inmemory_test.go new file mode 100644 index 000000000..5fdfbf31b --- /dev/null +++ b/cli/storage_inmemory_test.go @@ -0,0 +1,29 @@ +package cli_test + +import ( + "context" + + "github.com/alecthomas/kingpin" + + "github.com/kopia/kopia/cli" + "github.com/kopia/kopia/internal/repotesting" + "github.com/kopia/kopia/repo/blob" +) + +// storageInMemoryFlags is in-memory storage initialization flags for cli +// setup. +type storageInMemoryFlags struct { + options repotesting.ReconnectableStorageOptions +} + +func (c *storageInMemoryFlags) Setup(_ cli.StorageProviderServices, cmd *kingpin.CmdClause) { + cmd.Flag("uuid", "UUID of the reconnectable in-memory storage").Required().StringVar(&c.options.UUID) +} + +func (c *storageInMemoryFlags) Connect(ctx context.Context, isCreate bool, _ int) (blob.Storage, error) { + // nolint:wrapcheck + return blob.NewStorage(ctx, blob.ConnectionInfo{ + Type: repotesting.ReconnectableStorageType, + Config: &c.options, + }, isCreate) +} diff --git a/cli/storage_providers.go b/cli/storage_providers.go index 0b3d4eb68..d3be1909f 100644 --- a/cli/storage_providers.go +++ b/cli/storage_providers.go @@ -9,37 +9,43 @@ "github.com/kopia/kopia/repo/blob/throttling" ) -type storageProviderServices interface { +// StorageProviderServices is implemented by the cli App that allows the cli +// and tests to mutate the default storage providers. +type StorageProviderServices interface { + AddStorageProvider(p StorageProvider) + setPasswordFromToken(pwd string) + storageProviders() []StorageProvider } -type storageFlags interface { - setup(sps storageProviderServices, cmd *kingpin.CmdClause) - connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) +// StorageFlags is implemented by cli storage providers which need to support a +// particular backend. This requires the common setup and connection methods +// implemented by all the cli storage providers. +type StorageFlags interface { + Setup(sps StorageProviderServices, cmd *kingpin.CmdClause) + Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) } -type storageProvider struct { - name string - description string - newFlags func() storageFlags -} - -func cliStorageProviders() []storageProvider { - return []storageProvider{ - {"from-config", "the provided configuration file", func() storageFlags { return &storageFromConfigFlags{} }}, - - {"azure", "an Azure blob storage", func() storageFlags { return &storageAzureFlags{} }}, - {"b2", "a B2 bucket", func() storageFlags { return &storageB2Flags{} }}, - {"filesystem", "a filesystem", func() storageFlags { return &storageFilesystemFlags{} }}, - {"gcs", "a Google Cloud Storage bucket", func() storageFlags { return &storageGCSFlags{} }}, - {"rclone", "a rclone-based provided", func() storageFlags { return &storageRcloneFlags{} }}, - {"s3", "an S3 bucket", func() storageFlags { return &storageS3Flags{} }}, - {"sftp", "an SFTP storage", func() storageFlags { return &storageSFTPFlags{} }}, - {"webdav", "a WebDAV storage", func() storageFlags { return &storageWebDAVFlags{} }}, - } +// StorageProvider is a CLI provider for storage options and allows the CLI to +// multiplex between various provider CLI flag constructors. +type StorageProvider struct { + Name string + Description string + NewFlags func() StorageFlags } func commonThrottlingFlags(cmd *kingpin.CmdClause, limits *throttling.Limits) { cmd.Flag("max-download-speed", "Limit the download speed.").PlaceHolder("BYTES_PER_SEC").FloatVar(&limits.DownloadBytesPerSecond) cmd.Flag("max-upload-speed", "Limit the upload speed.").PlaceHolder("BYTES_PER_SEC").FloatVar(&limits.UploadBytesPerSecond) } + +// AddStorageProvider adds a new StorageProvider at runtime after the App has +// been initialized with the default providers. This is used in tests which +// require custom storage providers to simulate various edge cases. +func (c *App) AddStorageProvider(p StorageProvider) { + c.cliStorageProviders = append(c.cliStorageProviders, p) +} + +func (c *App) storageProviders() []StorageProvider { + return c.cliStorageProviders +} diff --git a/cli/storage_rclone.go b/cli/storage_rclone.go index 6c7e6a4a9..27eaaebdb 100644 --- a/cli/storage_rclone.go +++ b/cli/storage_rclone.go @@ -17,7 +17,7 @@ type storageRcloneFlags struct { embedRCloneConfigFile string } -func (c *storageRcloneFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageRcloneFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("remote-path", "Rclone remote:path").Required().StringVar(&c.opt.RemotePath) cmd.Flag("flat", "Use flat directory structure").BoolVar(&c.connectFlat) cmd.Flag("rclone-exe", "Path to rclone binary").StringVar(&c.opt.RCloneExe) @@ -32,7 +32,7 @@ func (c *storageRcloneFlags) setup(_ storageProviderServices, cmd *kingpin.CmdCl commonThrottlingFlags(cmd, &c.opt.Limits) } -func (c *storageRcloneFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageRcloneFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { c.opt.DirectoryShards = initialDirectoryShards(c.connectFlat, formatVersion) if c.embedRCloneConfigFile != "" { diff --git a/cli/storage_s3.go b/cli/storage_s3.go index 93b39f5cc..fc7342d2c 100644 --- a/cli/storage_s3.go +++ b/cli/storage_s3.go @@ -15,7 +15,7 @@ type storageS3Flags struct { s3options s3.Options } -func (c *storageS3Flags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageS3Flags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("bucket", "Name of the S3 bucket").Required().StringVar(&c.s3options.BucketName) cmd.Flag("endpoint", "Endpoint to use").Default("s3.amazonaws.com").StringVar(&c.s3options.Endpoint) cmd.Flag("region", "S3 Region").Default("").StringVar(&c.s3options.Region) @@ -46,7 +46,7 @@ func (c *storageS3Flags) setup(_ storageProviderServices, cmd *kingpin.CmdClause cmd.Flag("point-in-time", "Use a point-in-time view of the storage repository when supported").PlaceHolder(time.RFC3339).PreAction(pitPreAction).StringVar(&pointInTimeStr) } -func (c *storageS3Flags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageS3Flags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { if isCreate && c.s3options.PointInTime != nil && !c.s3options.PointInTime.IsZero() { return nil, errors.New("Cannot specify a 'point-in-time' option when creating a repository") } diff --git a/cli/storage_sftp.go b/cli/storage_sftp.go index 96c8a56b0..971a4d4fb 100644 --- a/cli/storage_sftp.go +++ b/cli/storage_sftp.go @@ -18,7 +18,7 @@ type storageSFTPFlags struct { embedCredentials bool } -func (c *storageSFTPFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageSFTPFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("path", "Path to the repository in the SFTP/SSH server").Required().StringVar(&c.options.Path) cmd.Flag("host", "SFTP/SSH server hostname").Required().StringVar(&c.options.Host) cmd.Flag("port", "SFTP/SSH server port").Default("22").IntVar(&c.options.Port) @@ -109,7 +109,7 @@ func (c *storageSFTPFlags) getOptions(formatVersion int) (*sftp.Options, error) return &sftpo, nil } -func (c *storageSFTPFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageSFTPFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { opt, err := c.getOptions(formatVersion) if err != nil { return nil, err diff --git a/cli/storage_webdav.go b/cli/storage_webdav.go index 472674c9b..b79531e55 100644 --- a/cli/storage_webdav.go +++ b/cli/storage_webdav.go @@ -15,7 +15,7 @@ type storageWebDAVFlags struct { connectFlat bool } -func (c *storageWebDAVFlags) setup(_ storageProviderServices, cmd *kingpin.CmdClause) { +func (c *storageWebDAVFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("url", "URL of WebDAV server").Required().StringVar(&c.options.URL) cmd.Flag("flat", "Use flat directory structure").BoolVar(&c.connectFlat) cmd.Flag("webdav-username", "WebDAV username").Envar("KOPIA_WEBDAV_USERNAME").StringVar(&c.options.Username) @@ -26,7 +26,7 @@ func (c *storageWebDAVFlags) setup(_ storageProviderServices, cmd *kingpin.CmdCl commonThrottlingFlags(cmd, &c.options.Limits) } -func (c *storageWebDAVFlags) connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { +func (c *storageWebDAVFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) { wo := c.options if wo.Username != "" && wo.Password == "" { diff --git a/internal/repotesting/reconnectable_storage.go b/internal/repotesting/reconnectable_storage.go index 8affd8625..7a73309f8 100644 --- a/internal/repotesting/reconnectable_storage.go +++ b/internal/repotesting/reconnectable_storage.go @@ -18,23 +18,25 @@ type reconnectableStorage struct { blob.Storage - opt *reconnectableStorageOptions + opt *ReconnectableStorageOptions } -const reconnectableStorageType = "reconnectable" +// ReconnectableStorageType is the unique storage type identifier for +// reconnectable storage backend. +const ReconnectableStorageType = "reconnectable" -// reconnectableStorageOptions provides options to reconnectable storage. -type reconnectableStorageOptions struct { +// ReconnectableStorageOptions provides options to reconnectable storage. +type ReconnectableStorageOptions struct { UUID string } -// newReconnectableStorage wraps the provided storage that may or may not be round-trippable +// NewReconnectableStorage wraps the provided storage that may or may not be round-trippable // in a wrapper that globally caches storage instance and ensures its connection info is // round-trippable. -func newReconnectableStorage(tb testing.TB, st blob.Storage) blob.Storage { +func NewReconnectableStorage(tb testing.TB, st blob.Storage) blob.Storage { tb.Helper() - st2 := reconnectableStorage{st, &reconnectableStorageOptions{UUID: uuid.NewString()}} + st2 := reconnectableStorage{st, &ReconnectableStorageOptions{UUID: uuid.NewString()}} reconnectableStorageByUUID.Store(st2.opt.UUID, st2) tb.Cleanup(func() { @@ -49,17 +51,17 @@ func newReconnectableStorage(tb testing.TB, st blob.Storage) blob.Storage { func (s reconnectableStorage) ConnectionInfo() blob.ConnectionInfo { return blob.ConnectionInfo{ - Type: reconnectableStorageType, + Type: ReconnectableStorageType, Config: s.opt, } } func init() { blob.AddSupportedStorage( - reconnectableStorageType, - func() interface{} { return &reconnectableStorageOptions{} }, + ReconnectableStorageType, + func() interface{} { return &ReconnectableStorageOptions{} }, func(ctx context.Context, o interface{}, isCreate bool) (blob.Storage, error) { - opt, ok := o.(*reconnectableStorageOptions) + opt, ok := o.(*ReconnectableStorageOptions) if !ok { return nil, errors.Errorf("invalid options %T", o) } diff --git a/internal/repotesting/repotesting.go b/internal/repotesting/repotesting.go index 166d7421c..756bf9329 100644 --- a/internal/repotesting/repotesting.go +++ b/internal/repotesting/repotesting.go @@ -84,7 +84,7 @@ func (e *Environment) setup(tb testing.TB, version content.FormatVersion, opts . st = blobtesting.NewVersionedMapStorage(openOpt.TimeNowFunc) } - st = newReconnectableStorage(tb, st) + st = NewReconnectableStorage(tb, st) e.st = st if e.Password == "" {