add e2e wiring of cli to kopia (#77)

* add e2e wiring of cli to kopia

Now that pkg/storage and internal/kopia are in place, we
can wire up the init flow from the cli all the way to kopia.  Testing
harness for this functionality still needs investigation afterward.

* factor out awsVars struct for s3Cfg
This commit is contained in:
Keepers 2022-05-25 14:06:05 -06:00 committed by GitHub
parent 569ba524ac
commit 548eb1be72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 87 deletions

View File

@ -61,7 +61,7 @@ var connectCmd = &cobra.Command{
Use: connectCommand, Use: connectCommand,
Short: "Connect to a repository.", Short: "Connect to a repository.",
Long: `Connect to an existing repository.`, Long: `Connect to an existing repository.`,
Run: handleInitCmd, Run: handleConnectCmd,
Args: cobra.NoArgs, Args: cobra.NoArgs,
} }

View File

@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/alcionai/corso/pkg/repository" "github.com/alcionai/corso/pkg/repository"
"github.com/alcionai/corso/pkg/repository/s3" "github.com/alcionai/corso/pkg/storage"
) )
// s3 bucket info from flags // s3 bucket info from flags
@ -45,26 +45,23 @@ var s3InitCmd = &cobra.Command{
// initializes a s3 repo. // initializes a s3 repo.
func initS3Cmd(cmd *cobra.Command, args []string) { func initS3Cmd(cmd *cobra.Command, args []string) {
mv := getM365Vars() mv := getM365Vars()
av := getAwsVars() s3Cfg := makeS3Config()
fmt.Printf( fmt.Printf(
"Called -\n`corso repo init s3`\nbucket:\t%s\nkey:\t%s\n356Client:\t%s\nfound 356Secret:\t%v\nfound awsSecret:\t%v\n", "Called -\n`corso repo init s3`\nbucket:\t%s\nkey:\t%s\n356Client:\t%s\nfound 356Secret:\t%v\nfound awsSecret:\t%v\n",
bucket, s3Cfg.Bucket,
av.accessKey, s3Cfg.AccessKey,
mv.clientID, mv.clientID,
len(mv.clientSecret) > 0, len(mv.clientSecret) > 0,
len(av.accessSecret) > 0) len(s3Cfg.SecretKey) > 0)
_, err := repository.Initialize( a := repository.Account{
cmd.Context(), TenantID: mv.tenantID,
repository.ProviderS3, ClientID: mv.clientID,
repository.Account{ ClientSecret: mv.clientSecret,
TenantID: mv.tenantID, }
ClientID: mv.clientID, s := storage.NewStorage(storage.ProviderS3, s3Cfg)
ClientSecret: mv.clientSecret,
}, if _, err := repository.Initialize(cmd.Context(), a, s); err != nil {
s3.NewConfig(av.bucket, av.accessKey, av.accessSecret),
)
if err != nil {
fmt.Printf("Failed to initialize a new S3 repository: %v", err) fmt.Printf("Failed to initialize a new S3 repository: %v", err)
os.Exit(1) os.Exit(1)
} }
@ -82,47 +79,37 @@ var s3ConnectCmd = &cobra.Command{
// connects to an existing s3 repo. // connects to an existing s3 repo.
func connectS3Cmd(cmd *cobra.Command, args []string) { func connectS3Cmd(cmd *cobra.Command, args []string) {
mv := getM365Vars() mv := getM365Vars()
av := getAwsVars() s3Cfg := makeS3Config()
fmt.Printf( fmt.Printf(
"Called -\n`corso repo connect s3`\nbucket:\t%s\nkey:\t%s\n356Client:\t%s\nfound 356Secret:\t%v\nfound awsSecret:\t%v\n", "Called -\n`corso repo connect s3`\nbucket:\t%s\nkey:\t%s\n356Client:\t%s\nfound 356Secret:\t%v\nfound awsSecret:\t%v\n",
bucket, s3Cfg.Bucket,
accessKey, s3Cfg.AccessKey,
mv.clientID, mv.clientID,
len(mv.clientSecret) > 0, len(mv.clientSecret) > 0,
len(av.accessSecret) > 0) len(s3Cfg.SecretKey) > 0)
_, err := repository.Connect( a := repository.Account{
cmd.Context(), TenantID: mv.tenantID,
repository.ProviderS3, ClientID: mv.clientID,
repository.Account{ ClientSecret: mv.clientSecret,
TenantID: mv.tenantID, }
ClientID: mv.clientID, s := storage.NewStorage(storage.ProviderS3, s3Cfg)
ClientSecret: mv.clientSecret,
}, if _, err := repository.Connect(cmd.Context(), a, s); err != nil {
s3.NewConfig(av.bucket, av.accessKey, av.accessSecret),
)
if err != nil {
fmt.Printf("Failed to connect to the S3 repository: %v", err) fmt.Printf("Failed to connect to the S3 repository: %v", err)
os.Exit(1) os.Exit(1)
} }
} }
// aggregates aws details from flag and env_var values.
type awsVars struct {
accessKey string
accessSecret string
bucket string
}
// helper for aggregating aws connection details. // helper for aggregating aws connection details.
func getAwsVars() awsVars { func makeS3Config() storage.S3Config {
ak := os.Getenv("AWS_ACCESS_KEY_ID") ak := os.Getenv("AWS_ACCESS_KEY_ID")
if len(accessKey) > 0 { if len(accessKey) > 0 {
ak = accessKey ak = accessKey
} }
return awsVars{ return storage.S3Config{
accessKey: ak, AccessKey: ak,
accessSecret: os.Getenv("AWS_SECRET_ACCESS_KEY"), SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
bucket: bucket, Bucket: bucket,
} }
} }

View File

@ -4,8 +4,9 @@ import (
"context" "context"
"time" "time"
"github.com/alcionai/corso/internal/kopia"
"github.com/alcionai/corso/pkg/storage"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kopia/kopia/repo/blob"
) )
type repoProvider int type repoProvider int
@ -22,9 +23,8 @@ type Repository struct {
CreatedAt time.Time CreatedAt time.Time
Version string // in case of future breaking changes Version string // in case of future breaking changes
Provider repoProvider // must be repository.S3Provider Account Account // the user's m365 account connection details
Account Account // the user's m365 account connection details Storage storage.Storage // the storage provider details and configuration
Config Config // provider-based configuration details
} }
// Account holds the user's m365 account details. // Account holds the user's m365 account details.
@ -34,10 +34,6 @@ type Account struct {
ClientSecret string ClientSecret string
} }
type Config interface {
KopiaStorage(ctx context.Context, create bool) (blob.Storage, error)
}
// Initialize will: // Initialize will:
// * validate the m365 account & secrets // * validate the m365 account & secrets
// * connect to the m365 account to ensure communication capability // * connect to the m365 account to ensure communication capability
@ -48,16 +44,18 @@ type Config interface {
// * return the connected repository // * return the connected repository
func Initialize( func Initialize(
ctx context.Context, ctx context.Context,
provider repoProvider,
acct Account, acct Account,
cfg Config, storage storage.Storage,
) (Repository, error) { ) (Repository, error) {
k := kopia.New(storage)
if err := k.Initialize(ctx); err != nil {
return Repository{}, err
}
r := Repository{ r := Repository{
ID: uuid.New(), ID: uuid.New(),
Version: "v1", Version: "v1",
Provider: provider, Account: acct,
Account: acct, Storage: storage,
Config: cfg,
} }
return r, nil return r, nil
} }
@ -69,16 +67,18 @@ func Initialize(
// * return the connected repository // * return the connected repository
func Connect( func Connect(
ctx context.Context, ctx context.Context,
provider repoProvider,
acct Account, acct Account,
cfg Config, storage storage.Storage,
) (Repository, error) { ) (Repository, error) {
k := kopia.New(storage)
if err := k.Connect(ctx); err != nil {
return Repository{}, err
}
// todo: ID and CreatedAt should get retrieved from a stored kopia config. // todo: ID and CreatedAt should get retrieved from a stored kopia config.
r := Repository{ r := Repository{
Version: "v1", Version: "v1",
Provider: provider, Account: acct,
Account: acct, Storage: storage,
Config: cfg,
} }
return r, nil return r, nil
} }

View File

@ -2,37 +2,55 @@ package repository_test
import ( import (
"context" "context"
"strings"
"testing" "testing"
"github.com/kopia/kopia/repo/blob"
"github.com/alcionai/corso/pkg/repository" "github.com/alcionai/corso/pkg/repository"
"github.com/alcionai/corso/pkg/storage"
) )
type testConfig struct{}
func (tc testConfig) KopiaStorage(ctx context.Context, create bool) (blob.Storage, error) {
return nil, nil
}
func TestInitialize(t *testing.T) { func TestInitialize(t *testing.T) {
_, err := repository.Initialize( table := []struct {
context.Background(), storage storage.Storage
repository.ProviderUnknown, account repository.Account
repository.Account{}, expectedErr string
testConfig{}) }{
if err != nil { {
t.Fatalf("didn't expect initialize to error, got [%v]", err) storage.NewStorage(storage.ProviderUnknown),
repository.Account{},
"provider details are required",
},
}
for _, test := range table {
t.Run(test.expectedErr, func(t *testing.T) {
_, err := repository.Initialize(context.Background(), test.account, test.storage)
if err == nil || !strings.Contains(err.Error(), test.expectedErr) {
t.Fatalf("expected error with [%s], got [%v]", test.expectedErr, err)
}
})
} }
} }
// repository.Connect involves end-to-end communication with kopia, therefore this only
// tests expected error cases from
func TestConnect(t *testing.T) { func TestConnect(t *testing.T) {
_, err := repository.Connect( table := []struct {
context.Background(), storage storage.Storage
repository.ProviderUnknown, account repository.Account
repository.Account{}, expectedErr string
testConfig{}) }{
if err != nil { {
t.Fatalf("didn't expect connect to error, got [%v]", err) storage.NewStorage(storage.ProviderUnknown),
repository.Account{},
"provider details are required",
},
}
for _, test := range table {
t.Run(test.expectedErr, func(t *testing.T) {
_, err := repository.Connect(context.Background(), test.account, test.storage)
if err == nil || !strings.Contains(err.Error(), test.expectedErr) {
t.Fatalf("expected error with [%s], got [%v]", test.expectedErr, err)
}
})
} }
} }