Allow overriding the default username/hostname in kopia (#3223)

This allows setting the username/hostname
kopia will use for maintenance. This
information is recorded in the kopia config
file during connect and reused during open

If no values are given, kopia pulls the
values from the OS

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #3077

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
ashmrtn 2023-04-27 15:47:35 -07:00 committed by GitHub
parent 754e14d7a6
commit 6ac8c9f331
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 103 additions and 29 deletions

View File

@ -18,6 +18,7 @@ import (
"github.com/kopia/kopia/snapshot/snapshotfs" "github.com/kopia/kopia/snapshot/snapshotfs"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/storage" "github.com/alcionai/corso/src/pkg/storage"
) )
@ -69,7 +70,7 @@ func NewConn(s storage.Storage) *conn {
} }
} }
func (w *conn) Initialize(ctx context.Context) error { func (w *conn) Initialize(ctx context.Context, opts control.RepoOptions) error {
bst, err := blobStoreByProvider(ctx, w.storage) bst, err := blobStoreByProvider(ctx, w.storage)
if err != nil { if err != nil {
return clues.Wrap(err, "initializing storage") return clues.Wrap(err, "initializing storage")
@ -92,6 +93,7 @@ func (w *conn) Initialize(ctx context.Context) error {
err = w.commonConnect( err = w.commonConnect(
ctx, ctx,
opts,
cfg.KopiaCfgDir, cfg.KopiaCfgDir,
bst, bst,
cfg.CorsoPassphrase, cfg.CorsoPassphrase,
@ -108,7 +110,7 @@ func (w *conn) Initialize(ctx context.Context) error {
return nil return nil
} }
func (w *conn) Connect(ctx context.Context) error { func (w *conn) Connect(ctx context.Context, opts control.RepoOptions) error {
bst, err := blobStoreByProvider(ctx, w.storage) bst, err := blobStoreByProvider(ctx, w.storage)
if err != nil { if err != nil {
return clues.Wrap(err, "initializing storage") return clues.Wrap(err, "initializing storage")
@ -122,6 +124,7 @@ func (w *conn) Connect(ctx context.Context) error {
return w.commonConnect( return w.commonConnect(
ctx, ctx,
opts,
cfg.KopiaCfgDir, cfg.KopiaCfgDir,
bst, bst,
cfg.CorsoPassphrase, cfg.CorsoPassphrase,
@ -131,16 +134,21 @@ func (w *conn) Connect(ctx context.Context) error {
func (w *conn) commonConnect( func (w *conn) commonConnect(
ctx context.Context, ctx context.Context,
opts control.RepoOptions,
configDir string, configDir string,
bst blob.Storage, bst blob.Storage,
password, compressor string, password, compressor string,
) error { ) error {
var opts *repo.ConnectOptions kopiaOpts := &repo.ConnectOptions{
ClientOptions: repo.ClientOptions{
Username: opts.User,
Hostname: opts.Host,
},
}
if len(configDir) > 0 { if len(configDir) > 0 {
opts = &repo.ConnectOptions{ kopiaOpts.CachingOptions = content.CachingOptions{
CachingOptions: content.CachingOptions{ CacheDirectory: configDir,
CacheDirectory: configDir,
},
} }
} else { } else {
configDir = defaultKopiaConfigDir configDir = defaultKopiaConfigDir
@ -154,7 +162,7 @@ func (w *conn) commonConnect(
cfgFile, cfgFile,
bst, bst,
password, password,
opts, kopiaOpts,
); err != nil { ); err != nil {
return clues.Wrap(err, "connecting to repo").WithClues(ctx) return clues.Wrap(err, "connecting to repo").WithClues(ctx)
} }

View File

@ -14,16 +14,18 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/storage" "github.com/alcionai/corso/src/pkg/storage"
) )
//revive:disable:context-as-argument func openKopiaRepo(
func openKopiaRepo(t *testing.T, ctx context.Context) (*conn, error) { t *testing.T,
//revive:enable:context-as-argument ctx context.Context, //revive:disable-line:context-as-argument
) (*conn, error) {
st := tester.NewPrefixedS3Storage(t) st := tester.NewPrefixedS3Storage(t)
k := NewConn(st) k := NewConn(st)
if err := k.Initialize(ctx); err != nil { if err := k.Initialize(ctx, control.RepoOptions{}); err != nil {
return nil, err return nil, err
} }
@ -77,13 +79,13 @@ func (suite *WrapperIntegrationSuite) TestRepoExistsError() {
st := tester.NewPrefixedS3Storage(t) st := tester.NewPrefixedS3Storage(t)
k := NewConn(st) k := NewConn(st)
err := k.Initialize(ctx) err := k.Initialize(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
err = k.Close(ctx) err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
err = k.Initialize(ctx) err = k.Initialize(ctx, control.RepoOptions{})
assert.Error(t, err, clues.ToCore(err)) assert.Error(t, err, clues.ToCore(err))
assert.ErrorIs(t, err, ErrorRepoAlreadyExists) assert.ErrorIs(t, err, ErrorRepoAlreadyExists)
} }
@ -97,7 +99,7 @@ func (suite *WrapperIntegrationSuite) TestBadProviderErrors() {
st.Provider = storage.ProviderUnknown st.Provider = storage.ProviderUnknown
k := NewConn(st) k := NewConn(st)
err := k.Initialize(ctx) err := k.Initialize(ctx, control.RepoOptions{})
assert.Error(t, err, clues.ToCore(err)) assert.Error(t, err, clues.ToCore(err))
} }
@ -109,7 +111,7 @@ func (suite *WrapperIntegrationSuite) TestConnectWithoutInitErrors() {
st := tester.NewPrefixedS3Storage(t) st := tester.NewPrefixedS3Storage(t)
k := NewConn(st) k := NewConn(st)
err := k.Connect(ctx) err := k.Connect(ctx, control.RepoOptions{})
assert.Error(t, err, clues.ToCore(err)) assert.Error(t, err, clues.ToCore(err))
} }
@ -356,7 +358,7 @@ func (suite *WrapperIntegrationSuite) TestConfigDefaultsSetOnInitAndNotOnConnect
err = k.Close(ctx) err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
err = k.Connect(ctx) err = k.Connect(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
defer func() { defer func() {
@ -384,9 +386,63 @@ func (suite *WrapperIntegrationSuite) TestInitAndConnWithTempDirectory() {
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
// Re-open with Connect. // Re-open with Connect.
err = k.Connect(ctx) err = k.Connect(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
err = k.Close(ctx) err = k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
} }
func (suite *WrapperIntegrationSuite) TestSetUserAndHost() {
ctx, flush := tester.NewContext()
defer flush()
opts := control.RepoOptions{
User: "foo",
Host: "bar",
}
t := suite.T()
st := tester.NewPrefixedS3Storage(t)
k := NewConn(st)
err := k.Initialize(ctx, opts)
require.NoError(t, err, clues.ToCore(err))
kopiaOpts := k.ClientOptions()
require.Equal(t, opts.User, kopiaOpts.Username)
require.Equal(t, opts.Host, kopiaOpts.Hostname)
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
// Re-open with Connect and a different user/hostname.
opts.User = "hello"
opts.Host = "world"
err = k.Connect(ctx, opts)
require.NoError(t, err, clues.ToCore(err))
kopiaOpts = k.ClientOptions()
require.Equal(t, opts.User, kopiaOpts.Username)
require.Equal(t, opts.Host, kopiaOpts.Hostname)
err = k.Close(ctx)
require.NoError(t, err, clues.ToCore(err))
// Make sure not setting the values uses the kopia defaults.
opts.User = ""
opts.Host = ""
err = k.Connect(ctx, opts)
require.NoError(t, err, clues.ToCore(err))
kopiaOpts = k.ClientOptions()
assert.NotEmpty(t, kopiaOpts.Username)
assert.NotEqual(t, "hello", kopiaOpts.Username)
assert.NotEmpty(t, kopiaOpts.Hostname)
assert.NotEqual(t, "world", kopiaOpts.Hostname)
err = k.Close(ctx)
assert.NoError(t, err, clues.ToCore(err))
}

View File

@ -17,6 +17,7 @@ import (
"github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/control"
) )
type fooModel struct { type fooModel struct {
@ -803,7 +804,7 @@ func openConnAndModelStore(
st := tester.NewPrefixedS3Storage(t) st := tester.NewPrefixedS3Storage(t)
c := NewConn(st) c := NewConn(st)
err := c.Initialize(ctx) err := c.Initialize(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
defer func() { defer func() {
@ -822,7 +823,7 @@ func reconnectToModelStore(
ctx context.Context, //revive:disable-line:context-as-argument ctx context.Context, //revive:disable-line:context-as-argument
c *conn, c *conn,
) *ModelStore { ) *ModelStore {
err := c.Connect(ctx) err := c.Connect(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
defer func() { defer func() {

View File

@ -83,7 +83,7 @@ func prepNewTestBackupOp(
k = kopia.NewConn(st) k = kopia.NewConn(st)
) )
err := k.Initialize(ctx) err := k.Initialize(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
// kopiaRef comes with a count of 1 and Wrapper bumps it again so safe // kopiaRef comes with a count of 1 and Wrapper bumps it again so safe

View File

@ -175,7 +175,7 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
suite.acct = tester.NewM365Account(t) suite.acct = tester.NewM365Account(t)
err := k.Initialize(ctx) err := k.Initialize(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
suite.kopiaCloser = func(ctx context.Context) { suite.kopiaCloser = func(ctx context.Context) {

View File

@ -12,6 +12,7 @@ import (
"github.com/alcionai/corso/src/internal/kopia" "github.com/alcionai/corso/src/internal/kopia"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -41,7 +42,7 @@ func (suite *StreamStoreIntgSuite) SetupSubTest() {
st := tester.NewPrefixedS3Storage(t) st := tester.NewPrefixedS3Storage(t)
k := kopia.NewConn(st) k := kopia.NewConn(st)
require.NoError(t, k.Initialize(ctx)) require.NoError(t, k.Initialize(ctx, control.RepoOptions{}))
suite.kcloser = func() { k.Close(ctx) } suite.kcloser = func() { k.Close(ctx) }

View File

@ -13,6 +13,7 @@ type Options struct {
SkipReduce bool `json:"skipReduce"` SkipReduce bool `json:"skipReduce"`
ToggleFeatures Toggles `json:"toggleFeatures"` ToggleFeatures Toggles `json:"toggleFeatures"`
Parallelism Parallelism `json:"parallelism"` Parallelism Parallelism `json:"parallelism"`
Repo RepoOptions `json:"repo"`
} }
type FailureBehavior string type FailureBehavior string
@ -33,6 +34,12 @@ const (
BestEffort FailureBehavior = "best-effort" 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. // Defaults provides an Options with the default values set.
func Defaults() Options { func Defaults() Options {
return Options{ return Options{

View File

@ -120,7 +120,7 @@ func Initialize(
}() }()
kopiaRef := kopia.NewConn(s) kopiaRef := kopia.NewConn(s)
if err := kopiaRef.Initialize(ctx); err != nil { if err := kopiaRef.Initialize(ctx, opts.Repo); err != nil {
// replace common internal errors so that sdk users can check results with errors.Is() // replace common internal errors so that sdk users can check results with errors.Is()
if errors.Is(err, kopia.ErrorRepoAlreadyExists) { if errors.Is(err, kopia.ErrorRepoAlreadyExists) {
return nil, clues.Stack(ErrorRepoAlreadyExists, err).WithClues(ctx) return nil, clues.Stack(ErrorRepoAlreadyExists, err).WithClues(ctx)
@ -202,7 +202,7 @@ func Connect(
defer close(complete) defer close(complete)
kopiaRef := kopia.NewConn(s) kopiaRef := kopia.NewConn(s)
if err := kopiaRef.Connect(ctx); err != nil { if err := kopiaRef.Connect(ctx, opts.Repo); err != nil {
return nil, clues.Wrap(err, "connecting kopia client") return nil, clues.Wrap(err, "connecting kopia client")
} }
// kopiaRef comes with a count of 1 and NewWrapper/NewModelStore bumps it again so safe // kopiaRef comes with a count of 1 and NewWrapper/NewModelStore bumps it again so safe

View File

@ -20,6 +20,7 @@ import (
"github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/internal/version"
"github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
@ -236,10 +237,10 @@ func (suite *RepositoryModelIntgSuite) SetupSuite() {
require.NotNil(t, k) require.NotNil(t, k)
err = k.Initialize(ctx) err = k.Initialize(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
err = k.Connect(ctx) err = k.Connect(ctx, control.RepoOptions{})
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
suite.kopiaCloser = func(ctx context.Context) { suite.kopiaCloser = func(ctx context.Context) {
@ -286,8 +287,8 @@ func (suite *RepositoryModelIntgSuite) TestGetRepositoryModel() {
k = kopia.NewConn(s) k = kopia.NewConn(s)
) )
require.NoError(t, k.Initialize(ctx)) require.NoError(t, k.Initialize(ctx, control.RepoOptions{}))
require.NoError(t, k.Connect(ctx)) require.NoError(t, k.Connect(ctx, control.RepoOptions{}))
defer k.Close(ctx) defer k.Close(ctx)