From b6ef2d526624a4eecc619ff82a754f772f229a49 Mon Sep 17 00:00:00 2001 From: ashmrtn Date: Mon, 1 May 2023 17:52:15 -0700 Subject: [PATCH] Create wrapper around kopia maintenance (#3224) Create a wrapper function around the kopia maintenance operation. Will allow users to run maintenance, though they still need to be careful not to run maintenance concurrently if they override the username and hostname that kopia uses --- #### Does this PR need a docs update or release note? - [ ] :white_check_mark: Yes, it's included - [ ] :clock1: Yes, but in a later PR - [x] :no_entry: No #### Type of change - [x] :sunflower: Feature - [ ] :bug: Bugfix - [ ] :world_map: Documentation - [ ] :robot: Supportability/Tests - [ ] :computer: CI/Deployment - [ ] :broom: Tech Debt/Cleanup #### Issue(s) * #3077 #### Test Plan - [ ] :muscle: Manual - [x] :zap: Unit test - [ ] :green_heart: E2E --- src/go.mod | 1 + src/go.sum | 1 + src/internal/kopia/conn.go | 8 +- src/internal/kopia/conn_test.go | 18 +-- src/internal/kopia/model_store_test.go | 6 +- src/internal/kopia/wrapper.go | 129 ++++++++++++++++++ src/internal/kopia/wrapper_test.go | 124 ++++++++++++++++- .../operations/backup_integration_test.go | 3 +- src/internal/operations/restore_test.go | 3 +- src/internal/streamstore/collectables_test.go | 4 +- src/pkg/control/options.go | 23 ++-- .../repository/maintenancesafety_string.go | 24 ++++ .../repository/maintenancetype_string.go | 24 ++++ src/pkg/control/repository/repo.go | 37 +++++ .../repository/repository_unexported_test.go | 10 +- 15 files changed, 375 insertions(+), 40 deletions(-) create mode 100644 src/pkg/control/repository/maintenancesafety_string.go create mode 100644 src/pkg/control/repository/maintenancetype_string.go create mode 100644 src/pkg/control/repository/repo.go diff --git a/src/go.mod b/src/go.mod index 6ed7b775d..5d4031704 100644 --- a/src/go.mod +++ b/src/go.mod @@ -44,6 +44,7 @@ require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/dnaeon/go-vcr v1.2.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gofrs/flock v0.8.1 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect diff --git a/src/go.sum b/src/go.sum index fe76814d3..fb5e3f84a 100644 --- a/src/go.sum +++ b/src/go.sum @@ -131,6 +131,7 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 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/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= diff --git a/src/internal/kopia/conn.go b/src/internal/kopia/conn.go index c04875f75..09d4a34ff 100644 --- a/src/internal/kopia/conn.go +++ b/src/internal/kopia/conn.go @@ -18,7 +18,7 @@ import ( "github.com/kopia/kopia/snapshot/snapshotfs" "github.com/pkg/errors" - "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/storage" ) @@ -70,7 +70,7 @@ func NewConn(s storage.Storage) *conn { } } -func (w *conn) Initialize(ctx context.Context, opts control.RepoOptions) error { +func (w *conn) Initialize(ctx context.Context, opts repository.Options) error { bst, err := blobStoreByProvider(ctx, w.storage) if err != nil { return clues.Wrap(err, "initializing storage") @@ -110,7 +110,7 @@ func (w *conn) Initialize(ctx context.Context, opts control.RepoOptions) error { return nil } -func (w *conn) Connect(ctx context.Context, opts control.RepoOptions) error { +func (w *conn) Connect(ctx context.Context, opts repository.Options) error { bst, err := blobStoreByProvider(ctx, w.storage) if err != nil { return clues.Wrap(err, "initializing storage") @@ -134,7 +134,7 @@ func (w *conn) Connect(ctx context.Context, opts control.RepoOptions) error { func (w *conn) commonConnect( ctx context.Context, - opts control.RepoOptions, + opts repository.Options, configDir string, bst blob.Storage, password, compressor string, diff --git a/src/internal/kopia/conn_test.go b/src/internal/kopia/conn_test.go index 2a8d06d49..fd619f6da 100644 --- a/src/internal/kopia/conn_test.go +++ b/src/internal/kopia/conn_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/alcionai/corso/src/internal/tester" - "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/storage" ) @@ -25,7 +25,7 @@ func openKopiaRepo( st := tester.NewPrefixedS3Storage(t) k := NewConn(st) - if err := k.Initialize(ctx, control.RepoOptions{}); err != nil { + if err := k.Initialize(ctx, repository.Options{}); err != nil { return nil, err } @@ -79,13 +79,13 @@ func (suite *WrapperIntegrationSuite) TestRepoExistsError() { st := tester.NewPrefixedS3Storage(t) k := NewConn(st) - err := k.Initialize(ctx, control.RepoOptions{}) + err := k.Initialize(ctx, repository.Options{}) require.NoError(t, err, clues.ToCore(err)) err = k.Close(ctx) require.NoError(t, err, clues.ToCore(err)) - err = k.Initialize(ctx, control.RepoOptions{}) + err = k.Initialize(ctx, repository.Options{}) assert.Error(t, err, clues.ToCore(err)) assert.ErrorIs(t, err, ErrorRepoAlreadyExists) } @@ -99,7 +99,7 @@ func (suite *WrapperIntegrationSuite) TestBadProviderErrors() { st.Provider = storage.ProviderUnknown k := NewConn(st) - err := k.Initialize(ctx, control.RepoOptions{}) + err := k.Initialize(ctx, repository.Options{}) assert.Error(t, err, clues.ToCore(err)) } @@ -111,7 +111,7 @@ func (suite *WrapperIntegrationSuite) TestConnectWithoutInitErrors() { st := tester.NewPrefixedS3Storage(t) k := NewConn(st) - err := k.Connect(ctx, control.RepoOptions{}) + err := k.Connect(ctx, repository.Options{}) assert.Error(t, err, clues.ToCore(err)) } @@ -358,7 +358,7 @@ func (suite *WrapperIntegrationSuite) TestConfigDefaultsSetOnInitAndNotOnConnect err = k.Close(ctx) require.NoError(t, err, clues.ToCore(err)) - err = k.Connect(ctx, control.RepoOptions{}) + err = k.Connect(ctx, repository.Options{}) require.NoError(t, err, clues.ToCore(err)) defer func() { @@ -386,7 +386,7 @@ func (suite *WrapperIntegrationSuite) TestInitAndConnWithTempDirectory() { require.NoError(t, err, clues.ToCore(err)) // Re-open with Connect. - err = k.Connect(ctx, control.RepoOptions{}) + err = k.Connect(ctx, repository.Options{}) require.NoError(t, err, clues.ToCore(err)) err = k.Close(ctx) @@ -397,7 +397,7 @@ func (suite *WrapperIntegrationSuite) TestSetUserAndHost() { ctx, flush := tester.NewContext() defer flush() - opts := control.RepoOptions{ + opts := repository.Options{ User: "foo", Host: "bar", } diff --git a/src/internal/kopia/model_store_test.go b/src/internal/kopia/model_store_test.go index 4922dbe95..b5bf76bcd 100644 --- a/src/internal/kopia/model_store_test.go +++ b/src/internal/kopia/model_store_test.go @@ -17,7 +17,7 @@ import ( "github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/backup" - "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/control/repository" ) type fooModel struct { @@ -804,7 +804,7 @@ func openConnAndModelStore( st := tester.NewPrefixedS3Storage(t) c := NewConn(st) - err := c.Initialize(ctx, control.RepoOptions{}) + err := c.Initialize(ctx, repository.Options{}) require.NoError(t, err, clues.ToCore(err)) defer func() { @@ -823,7 +823,7 @@ func reconnectToModelStore( ctx context.Context, //revive:disable-line:context-as-argument c *conn, ) *ModelStore { - err := c.Connect(ctx, control.RepoOptions{}) + err := c.Connect(ctx, repository.Options{}) require.NoError(t, err, clues.ToCore(err)) defer func() { diff --git a/src/internal/kopia/wrapper.go b/src/internal/kopia/wrapper.go index 293b8df02..ff7280d1b 100644 --- a/src/internal/kopia/wrapper.go +++ b/src/internal/kopia/wrapper.go @@ -7,15 +7,19 @@ import ( "github.com/alcionai/clues" "github.com/kopia/kopia/fs" "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/repo/maintenance" "github.com/kopia/kopia/repo/manifest" "github.com/kopia/kopia/snapshot" "github.com/kopia/kopia/snapshot/policy" "github.com/kopia/kopia/snapshot/snapshotfs" + "github.com/kopia/kopia/snapshot/snapshotmaintenance" "github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/diagnostics" + "github.com/alcionai/corso/src/internal/observe" "github.com/alcionai/corso/src/internal/stats" "github.com/alcionai/corso/src/pkg/backup/details" + "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/path" @@ -515,3 +519,128 @@ func isErrEntryNotFound(err error) bool { return strings.Contains(err.Error(), "entry not found") && !strings.Contains(err.Error(), "parent is not a directory") } + +func (w Wrapper) Maintenance( + ctx context.Context, + opts repository.Maintenance, +) error { + kopiaSafety, err := translateSafety(opts.Safety) + if err != nil { + return clues.Wrap(err, "identifying safety level") + } + + mode, err := translateMode(opts.Type) + if err != nil { + return clues.Wrap(err, "identifying maintenance mode") + } + + currentOwner := w.c.ClientOptions().UsernameAtHost() + + ctx = clues.Add( + ctx, + "kopia_safety", kopiaSafety, + "kopia_maintenance_mode", mode, + "force", opts.Force, + "current_local_owner", clues.Hide(currentOwner)) + + dr, ok := w.c.Repository.(repo.DirectRepository) + if !ok { + return clues.New("unable to get valid handle to repo").WithClues(ctx) + } + + // Below write session options pulled from kopia's CLI code that runs + // maintenance. + err = repo.DirectWriteSession( + ctx, + dr, + repo.WriteSessionOptions{ + Purpose: "Corso maintenance", + }, + func(ctx context.Context, dw repo.DirectRepositoryWriter) error { + params, err := maintenance.GetParams(ctx, w.c) + if err != nil { + return clues.Wrap(err, "getting maintenance user@host").WithClues(ctx) + } + + // Need to do some fixup here as the user/host may not have been set. + if len(params.Owner) == 0 || (params.Owner != currentOwner && opts.Force) { + observe.Message( + ctx, + "updating maintenance user@host to ", + clues.Hide(currentOwner)) + + if err := w.setMaintenanceParams(ctx, dw, params, currentOwner); err != nil { + return clues.Wrap(err, "updating maintenance parameters"). + WithClues(ctx) + } + } + + ctx = clues.Add(ctx, "expected_owner", clues.Hide(params.Owner)) + + logger.Ctx(ctx).Info("running kopia maintenance") + + err = snapshotmaintenance.Run(ctx, dw, mode, opts.Force, kopiaSafety) + if err != nil { + return clues.Wrap(err, "running kopia maintenance").WithClues(ctx) + } + + return nil + }) + if err != nil { + return err + } + + return nil +} + +func translateSafety( + s repository.MaintenanceSafety, +) (maintenance.SafetyParameters, error) { + switch s { + case repository.FullMaintenanceSafety: + return maintenance.SafetyFull, nil + case repository.NoMaintenanceSafety: + return maintenance.SafetyNone, nil + default: + return maintenance.SafetyParameters{}, clues.New("bad safety value"). + With("input_safety", s.String()) + } +} + +func translateMode(t repository.MaintenanceType) (maintenance.Mode, error) { + switch t { + case repository.CompleteMaintenance: + return maintenance.ModeFull, nil + + case repository.MetadataMaintenance: + return maintenance.ModeQuick, nil + + default: + return maintenance.ModeNone, clues.New("bad maintenance type"). + With("input_maintenance_type", t.String()) + } +} + +// setMaintenanceUserHost sets the user and host for maintenance to the the +// user and host in the kopia config. +func (w Wrapper) setMaintenanceParams( + ctx context.Context, + drw repo.DirectRepositoryWriter, + p *maintenance.Params, + userAtHost string, +) error { + // This will source user/host from the kopia config file or fallback to + // fetching the values from the OS. + p.Owner = userAtHost + // Disable automatic maintenance for now since it can start matching on the + // user/host of at least one machine now. + p.QuickCycle.Enabled = false + p.FullCycle.Enabled = false + + err := maintenance.SetParams(ctx, drw, p) + if err != nil { + return clues.Wrap(err, "setting maintenance user/host") + } + + return nil +} diff --git a/src/internal/kopia/wrapper_test.go b/src/internal/kopia/wrapper_test.go index 1da2f1a84..89e5b134b 100644 --- a/src/internal/kopia/wrapper_test.go +++ b/src/internal/kopia/wrapper_test.go @@ -10,6 +10,7 @@ import ( "github.com/alcionai/clues" "github.com/google/uuid" "github.com/kopia/kopia/repo" + "github.com/kopia/kopia/repo/maintenance" "github.com/kopia/kopia/repo/manifest" "github.com/kopia/kopia/snapshot" "github.com/stretchr/testify/assert" @@ -23,6 +24,7 @@ import ( "github.com/alcionai/corso/src/internal/data/mock" "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/backup/details" + "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/path" @@ -143,7 +145,127 @@ func (suite *KopiaUnitSuite) TestCloseWithoutInitDoesNotPanic() { } // --------------- -// integration tests that use kopia +// integration tests that use kopia. +// --------------- +type BasicKopiaIntegrationSuite struct { + tester.Suite +} + +func TestBasicKopiaIntegrationSuite(t *testing.T) { + suite.Run(t, &BasicKopiaIntegrationSuite{ + Suite: tester.NewIntegrationSuite( + t, + [][]string{tester.AWSStorageCredEnvs}, + ), + }) +} + +// TestMaintenance checks that different username/hostname pairs will or won't +// cause maintenance to run. It treats kopia maintenance as a black box and +// only checks the returned error. +func (suite *BasicKopiaIntegrationSuite) TestMaintenance_FirstRun_NoChanges() { + ctx, flush := tester.NewContext() + defer flush() + + t := suite.T() + + k, err := openKopiaRepo(t, ctx) + require.NoError(t, err, clues.ToCore(err)) + + w := &Wrapper{k} + + opts := repository.Maintenance{ + Safety: repository.FullMaintenanceSafety, + Type: repository.MetadataMaintenance, + } + + err = w.Maintenance(ctx, opts) + require.NoError(t, err, clues.ToCore(err)) +} + +func (suite *BasicKopiaIntegrationSuite) TestMaintenance_WrongUser_NoForce_Fails() { + ctx, flush := tester.NewContext() + defer flush() + + t := suite.T() + + k, err := openKopiaRepo(t, ctx) + require.NoError(t, err, clues.ToCore(err)) + + w := &Wrapper{k} + + mOpts := repository.Maintenance{ + Safety: repository.FullMaintenanceSafety, + Type: repository.MetadataMaintenance, + } + + // This will set the user. + err = w.Maintenance(ctx, mOpts) + require.NoError(t, err, clues.ToCore(err)) + + err = k.Close(ctx) + require.NoError(t, err, clues.ToCore(err)) + + opts := repository.Options{ + User: "foo", + Host: "bar", + } + + err = k.Connect(ctx, opts) + require.NoError(t, err, clues.ToCore(err)) + + var notOwnedErr maintenance.NotOwnedError + + err = w.Maintenance(ctx, mOpts) + assert.ErrorAs(t, err, ¬OwnedErr, clues.ToCore(err)) +} + +func (suite *BasicKopiaIntegrationSuite) TestMaintenance_WrongUser_Force_Succeeds() { + ctx, flush := tester.NewContext() + defer flush() + + t := suite.T() + + k, err := openKopiaRepo(t, ctx) + require.NoError(t, err, clues.ToCore(err)) + + w := &Wrapper{k} + + mOpts := repository.Maintenance{ + Safety: repository.FullMaintenanceSafety, + Type: repository.MetadataMaintenance, + } + + // This will set the user. + err = w.Maintenance(ctx, mOpts) + require.NoError(t, err, clues.ToCore(err)) + + err = k.Close(ctx) + require.NoError(t, err, clues.ToCore(err)) + + opts := repository.Options{ + User: "foo", + Host: "bar", + } + + err = k.Connect(ctx, opts) + require.NoError(t, err, clues.ToCore(err)) + + mOpts.Force = true + + // This will set the user. + err = w.Maintenance(ctx, mOpts) + require.NoError(t, err, clues.ToCore(err)) + + mOpts.Force = false + + // Running without force should succeed now. + err = w.Maintenance(ctx, mOpts) + require.NoError(t, err, clues.ToCore(err)) +} + +// --------------- +// integration tests that use kopia and initialize a repo // --------------- type KopiaIntegrationSuite struct { tester.Suite diff --git a/src/internal/operations/backup_integration_test.go b/src/internal/operations/backup_integration_test.go index 5fdda1f71..abe89e56e 100644 --- a/src/internal/operations/backup_integration_test.go +++ b/src/internal/operations/backup_integration_test.go @@ -41,6 +41,7 @@ import ( "github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/selectors" @@ -83,7 +84,7 @@ func prepNewTestBackupOp( k = kopia.NewConn(st) ) - err := k.Initialize(ctx, control.RepoOptions{}) + err := k.Initialize(ctx, repository.Options{}) require.NoError(t, err, clues.ToCore(err)) // kopiaRef comes with a count of 1 and Wrapper bumps it again so safe diff --git a/src/internal/operations/restore_test.go b/src/internal/operations/restore_test.go index 5eb06dabb..c221fe4dc 100644 --- a/src/internal/operations/restore_test.go +++ b/src/internal/operations/restore_test.go @@ -28,6 +28,7 @@ import ( "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/store" ) @@ -175,7 +176,7 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() { suite.acct = tester.NewM365Account(t) - err := k.Initialize(ctx, control.RepoOptions{}) + err := k.Initialize(ctx, repository.Options{}) require.NoError(t, err, clues.ToCore(err)) suite.kopiaCloser = func(ctx context.Context) { diff --git a/src/internal/streamstore/collectables_test.go b/src/internal/streamstore/collectables_test.go index 640aa6406..6cefb804a 100644 --- a/src/internal/streamstore/collectables_test.go +++ b/src/internal/streamstore/collectables_test.go @@ -12,7 +12,7 @@ import ( "github.com/alcionai/corso/src/internal/kopia" "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/backup/details" - "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" ) @@ -42,7 +42,7 @@ func (suite *StreamStoreIntgSuite) SetupSubTest() { st := tester.NewPrefixedS3Storage(t) k := kopia.NewConn(st) - require.NoError(t, k.Initialize(ctx, control.RepoOptions{})) + require.NoError(t, k.Initialize(ctx, repository.Options{})) suite.kcloser = func() { k.Close(ctx) } diff --git a/src/pkg/control/options.go b/src/pkg/control/options.go index 94b2e316c..d805f8bf3 100644 --- a/src/pkg/control/options.go +++ b/src/pkg/control/options.go @@ -2,18 +2,19 @@ package control import ( "github.com/alcionai/corso/src/internal/common" + "github.com/alcionai/corso/src/pkg/control/repository" ) // Options holds the optional configurations for a process type Options struct { - Collision CollisionPolicy `json:"-"` - DisableMetrics bool `json:"disableMetrics"` - FailureHandling FailureBehavior `json:"failureHandling"` - RestorePermissions bool `json:"restorePermissions"` - SkipReduce bool `json:"skipReduce"` - ToggleFeatures Toggles `json:"toggleFeatures"` - Parallelism Parallelism `json:"parallelism"` - Repo RepoOptions `json:"repo"` + Collision CollisionPolicy `json:"-"` + DisableMetrics bool `json:"disableMetrics"` + FailureHandling FailureBehavior `json:"failureHandling"` + RestorePermissions bool `json:"restorePermissions"` + SkipReduce bool `json:"skipReduce"` + ToggleFeatures Toggles `json:"toggleFeatures"` + Parallelism Parallelism `json:"parallelism"` + Repo repository.Options `json:"repo"` } type FailureBehavior string @@ -34,12 +35,6 @@ const ( BestEffort FailureBehavior = "best-effort" ) -// Repo represents options that are specific to the repo storing backed up data. -type RepoOptions struct { - User string `json:"user"` - Host string `json:"host"` -} - // Defaults provides an Options with the default values set. func Defaults() Options { return Options{ diff --git a/src/pkg/control/repository/maintenancesafety_string.go b/src/pkg/control/repository/maintenancesafety_string.go new file mode 100644 index 000000000..789bd918a --- /dev/null +++ b/src/pkg/control/repository/maintenancesafety_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type=MaintenanceSafety -linecomment"; DO NOT EDIT. + +package repository + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[FullMaintenanceSafety-0] + _ = x[NoMaintenanceSafety-1] +} + +const _MaintenanceSafety_name = "FullMaintenanceSafetyNoMaintenanceSafety" + +var _MaintenanceSafety_index = [...]uint8{0, 21, 40} + +func (i MaintenanceSafety) String() string { + if i < 0 || i >= MaintenanceSafety(len(_MaintenanceSafety_index)-1) { + return "MaintenanceSafety(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _MaintenanceSafety_name[_MaintenanceSafety_index[i]:_MaintenanceSafety_index[i+1]] +} diff --git a/src/pkg/control/repository/maintenancetype_string.go b/src/pkg/control/repository/maintenancetype_string.go new file mode 100644 index 000000000..fea525c93 --- /dev/null +++ b/src/pkg/control/repository/maintenancetype_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type=MaintenanceType -linecomment"; DO NOT EDIT. + +package repository + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[CompleteMaintenance-0] + _ = x[MetadataMaintenance-1] +} + +const _MaintenanceType_name = "completemetadata" + +var _MaintenanceType_index = [...]uint8{0, 8, 16} + +func (i MaintenanceType) String() string { + if i < 0 || i >= MaintenanceType(len(_MaintenanceType_index)-1) { + return "MaintenanceType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _MaintenanceType_name[_MaintenanceType_index[i]:_MaintenanceType_index[i+1]] +} diff --git a/src/pkg/control/repository/repo.go b/src/pkg/control/repository/repo.go new file mode 100644 index 000000000..6153b7422 --- /dev/null +++ b/src/pkg/control/repository/repo.go @@ -0,0 +1,37 @@ +package repository + +// Repo represents options that are specific to the repo storing backed up data. +type Options struct { + User string `json:"user"` + Host string `json:"host"` +} + +type Maintenance struct { + Type MaintenanceType `json:"type"` + Safety MaintenanceSafety `json:"safety"` + Force bool `json:"force"` +} + +// --------------------------------------------------------------------------- +// Maintenance flags +// --------------------------------------------------------------------------- + +type MaintenanceType int + +// Can't be reordered as we rely on iota for numbering. +// +//go:generate stringer -type=MaintenanceType -linecomment +const ( + CompleteMaintenance MaintenanceType = iota // complete + MetadataMaintenance // metadata +) + +type MaintenanceSafety int + +// Can't be reordered as we rely on iota for numbering. +// +//go:generate stringer -type=MaintenanceSafety -linecomment +const ( + FullMaintenanceSafety MaintenanceSafety = iota + NoMaintenanceSafety +) diff --git a/src/pkg/repository/repository_unexported_test.go b/src/pkg/repository/repository_unexported_test.go index 6d05a7b48..92fbd86d5 100644 --- a/src/pkg/repository/repository_unexported_test.go +++ b/src/pkg/repository/repository_unexported_test.go @@ -20,7 +20,7 @@ import ( "github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup/details" - "github.com/alcionai/corso/src/pkg/control" + rep "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/selectors" @@ -237,10 +237,10 @@ func (suite *RepositoryModelIntgSuite) SetupSuite() { require.NotNil(t, k) - err = k.Initialize(ctx, control.RepoOptions{}) + err = k.Initialize(ctx, rep.Options{}) require.NoError(t, err, clues.ToCore(err)) - err = k.Connect(ctx, control.RepoOptions{}) + err = k.Connect(ctx, rep.Options{}) require.NoError(t, err, clues.ToCore(err)) suite.kopiaCloser = func(ctx context.Context) { @@ -287,8 +287,8 @@ func (suite *RepositoryModelIntgSuite) TestGetRepositoryModel() { k = kopia.NewConn(s) ) - require.NoError(t, k.Initialize(ctx, control.RepoOptions{})) - require.NoError(t, k.Connect(ctx, control.RepoOptions{})) + require.NoError(t, k.Initialize(ctx, rep.Options{})) + require.NoError(t, k.Connect(ctx, rep.Options{})) defer k.Close(ctx)