corso/src/cli/repo/s3.go
neha_gupta 8c661164ad
add flags for azure and aws (#3590)
<!-- PR description-->
Flags for all configs-

Azure cred flags- (azure-tenant-id, azure-client-id, azure-client-secret) present in -
- Backup (create, delete, details and list) and restore of Exchange, Onedrive and Sharepoint command
-  S3 repo init and connect command

AWS cred flags - (aws-access-key, aws-secret-access-key, aws-session-token) present in- 
- Backup (create, delete, details and list) and restore of Exchange, Onedrive and Sharepoint command
-  S3 repo init and connect command

Passphrase flag- (--passphrase) present in- 
- Backup (create, delete, details and list) and restore of Exchange, Onedrive and Sharepoint command
-  S3 repo init and connect command

S3 flags- 
--endpoint, --prefix, --bucket, --disable-tls, --disable-tls-verification - flags is for repo init and connect commands
all the S3 env var will also work only in case of repo init and connect command. For all other commands  user first connects to repo. Which will store the config values in config file. And then user can use that config file for other commands.

No cred configs are save in the config file by Corso. 

Config file values added- 
Azure cred - 
- azure_client_id 
- azure_secret 
- azure_tenantid

AWS cred -
- aws_access_key_id
- aws_secret_access_key
- aws_session_token

Passphrase -
- passphrase

**NOTE:** 
- in case of AWS creds all the three values should be provided from same method. Either put all values in env, config file and so on.
- all the S3 env var will also work only in case of repo init and connect command. For all other commands  user first connects to repo. Which will store the config values in config file. And then user can use that config file for other commands.



---

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

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

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature


#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* https://github.com/alcionai/corso/issues/3522

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
2023-06-29 05:30:15 +00:00

267 lines
8.5 KiB
Go

package repo
import (
"os"
"strconv"
"strings"
"github.com/alcionai/clues"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/cli/flags"
. "github.com/alcionai/corso/src/cli/print"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/internal/common/str"
"github.com/alcionai/corso/src/internal/events"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/credentials"
"github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/storage"
)
// s3 bucket info from flags
var (
succeedIfExists bool
bucket string
endpoint string
prefix string
doNotUseTLS bool
doNotVerifyTLS bool
)
// called by repo.go to map subcommands to provider-specific handling.
func addS3Commands(cmd *cobra.Command) *cobra.Command {
var (
c *cobra.Command
fs *pflag.FlagSet
)
switch cmd.Use {
case initCommand:
c, fs = utils.AddCommand(cmd, s3InitCmd())
case connectCommand:
c, fs = utils.AddCommand(cmd, s3ConnectCmd())
}
c.Use = c.Use + " " + s3ProviderCommandUseSuffix
c.SetUsageTemplate(cmd.UsageTemplate())
flags.AddAWSCredsFlags(c)
flags.AddAzureCredsFlags(c)
flags.AddCorsoPassphaseFlags(c)
// Flags addition ordering should follow the order we want them to appear in help and docs:
// More generic and more frequently used flags take precedence.
fs.StringVar(&bucket, "bucket", "", "Name of S3 bucket for repo. (required)")
fs.StringVar(&prefix, "prefix", "", "Repo prefix within bucket.")
fs.StringVar(&endpoint, "endpoint", "s3.amazonaws.com", "S3 service endpoint.")
fs.BoolVar(&doNotUseTLS, "disable-tls", false, "Disable TLS (HTTPS)")
fs.BoolVar(&doNotVerifyTLS, "disable-tls-verification", false, "Disable TLS (HTTPS) certificate verification.")
// In general, we don't want to expose this flag to users and have them mistake it
// for a broad-scale idempotency solution. We can un-hide it later the need arises.
fs.BoolVar(&succeedIfExists, "succeed-if-exists", false, "Exit with success if the repo has already been initialized.")
cobra.CheckErr(fs.MarkHidden("succeed-if-exists"))
return c
}
const (
s3ProviderCommand = "s3"
s3ProviderCommandUseSuffix = "--bucket <bucket>"
)
const (
s3ProviderCommandInitExamples = `# Create a new Corso repo in AWS S3 bucket named "my-bucket"
corso repo init s3 --bucket my-bucket
# Create a new Corso repo in AWS S3 bucket named "my-bucket" using a prefix
corso repo init s3 --bucket my-bucket --prefix my-prefix
# Create a new Corso repo in an S3 compliant storage provider
corso repo init s3 --bucket my-bucket --endpoint my-s3-server-endpoint`
s3ProviderCommandConnectExamples = `# Connect to a Corso repo in AWS S3 bucket named "my-bucket"
corso repo connect s3 --bucket my-bucket
# Connect to a Corso repo in AWS S3 bucket named "my-bucket" using a prefix
corso repo connect s3 --bucket my-bucket --prefix my-prefix
# Connect to a Corso repo in an S3 compliant storage provider
corso repo connect s3 --bucket my-bucket --endpoint my-s3-server-endpoint`
)
// ---------------------------------------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------------------------------------
// `corso repo init s3 [<flag>...]`
func s3InitCmd() *cobra.Command {
return &cobra.Command{
Use: s3ProviderCommand,
Short: "Initialize a S3 repository",
Long: `Bootstraps a new S3 repository and connects it to your m365 account.`,
RunE: initS3Cmd,
Args: cobra.NoArgs,
Example: s3ProviderCommandInitExamples,
}
}
// initializes a s3 repo.
func initS3Cmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
// s3 values from flags
s3Override := S3Overrides()
// s3 values from envs
s3Override = S3UpdateFromEnvVar(s3Override)
cfg, err := config.GetConfigRepoDetails(ctx, true, false, s3Override)
if err != nil {
return Only(ctx, err)
}
// SendStartCorsoEvent uses distict ID as tenant ID because repoID is still not generated
utils.SendStartCorsoEvent(
ctx,
cfg.Storage,
cfg.Account.ID(),
map[string]any{"command": "init repo"},
cfg.Account.ID(),
utils.Control())
s3Cfg, err := cfg.Storage.S3Config()
if err != nil {
return Only(ctx, clues.Wrap(err, "Retrieving s3 configuration"))
}
if strings.HasPrefix(s3Cfg.Endpoint, "http://") || strings.HasPrefix(s3Cfg.Endpoint, "https://") {
invalidEndpointErr := "endpoint doesn't support specifying protocol. " +
"pass --disable-tls flag to use http:// instead of default https://"
return Only(ctx, clues.New(invalidEndpointErr))
}
m365, err := cfg.Account.M365Config()
if err != nil {
return Only(ctx, clues.Wrap(err, "Failed to parse m365 account config"))
}
r, err := repository.Initialize(ctx, cfg.Account, cfg.Storage, utils.Control())
if err != nil {
if succeedIfExists && errors.Is(err, repository.ErrorRepoAlreadyExists) {
return nil
}
return Only(ctx, clues.Wrap(err, "Failed to initialize a new S3 repository"))
}
defer utils.CloseRepo(ctx, r)
Infof(ctx, "Initialized a S3 repository within bucket %s.", s3Cfg.Bucket)
if err = config.WriteRepoConfig(ctx, s3Cfg, m365, r.GetID()); err != nil {
return Only(ctx, clues.Wrap(err, "Failed to write repository configuration"))
}
return nil
}
// ---------------------------------------------------------------------------------------------------------
// Connect
// ---------------------------------------------------------------------------------------------------------
// `corso repo connect s3 [<flag>...]`
func s3ConnectCmd() *cobra.Command {
return &cobra.Command{
Use: s3ProviderCommand,
Short: "Connect to a S3 repository",
Long: `Ensures a connection to an existing S3 repository.`,
RunE: connectS3Cmd,
Args: cobra.NoArgs,
Example: s3ProviderCommandConnectExamples,
}
}
// connects to an existing s3 repo.
func connectS3Cmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
// s3 values from flags
s3Override := S3Overrides()
// s3 values from envs
s3Override = S3UpdateFromEnvVar(s3Override)
cfg, err := config.GetConfigRepoDetails(ctx, true, true, s3Override)
if err != nil {
return Only(ctx, err)
}
repoID := cfg.RepoID
if len(repoID) == 0 {
repoID = events.RepoIDNotFound
}
s3Cfg, err := cfg.Storage.S3Config()
if err != nil {
return Only(ctx, clues.Wrap(err, "Retrieving s3 configuration"))
}
m365, err := cfg.Account.M365Config()
if err != nil {
return Only(ctx, clues.Wrap(err, "Failed to parse m365 account config"))
}
if strings.HasPrefix(s3Cfg.Endpoint, "http://") || strings.HasPrefix(s3Cfg.Endpoint, "https://") {
invalidEndpointErr := "endpoint doesn't support specifying protocol. " +
"pass --disable-tls flag to use http:// instead of default https://"
return Only(ctx, clues.New(invalidEndpointErr))
}
r, err := repository.ConnectAndSendConnectEvent(ctx, cfg.Account, cfg.Storage, repoID, utils.Control())
if err != nil {
return Only(ctx, clues.Wrap(err, "Failed to connect to the S3 repository"))
}
defer utils.CloseRepo(ctx, r)
Infof(ctx, "Connected to S3 bucket %s.", s3Cfg.Bucket)
if err = config.WriteRepoConfig(ctx, s3Cfg, m365, r.GetID()); err != nil {
return Only(ctx, clues.Wrap(err, "Failed to write repository configuration"))
}
return nil
}
func S3Overrides() map[string]string {
return map[string]string{
config.AccountProviderTypeKey: account.ProviderM365.String(),
config.StorageProviderTypeKey: storage.ProviderS3.String(),
credentials.AWSAccessKeyID: flags.AWSAccessKeyFV,
credentials.AWSSecretAccessKey: flags.AWSSecretAccessKeyFV,
credentials.AWSSessionToken: flags.AWSSessionTokenFV,
storage.Bucket: bucket,
storage.Endpoint: endpoint,
storage.Prefix: prefix,
storage.DoNotUseTLS: strconv.FormatBool(doNotUseTLS),
storage.DoNotVerifyTLS: strconv.FormatBool(doNotVerifyTLS),
}
}
func S3UpdateFromEnvVar(s3Flag map[string]string) map[string]string {
s3Flag[storage.Bucket] = str.First(s3Flag[storage.Bucket], os.Getenv(storage.BucketKey))
s3Flag[storage.Endpoint] = str.First(s3Flag[storage.Endpoint], os.Getenv(storage.EndpointKey))
s3Flag[storage.Prefix] = str.First(s3Flag[storage.Prefix], os.Getenv(storage.PrefixKey))
s3Flag[storage.DoNotUseTLS] = str.First(s3Flag[storage.DoNotUseTLS], os.Getenv(storage.DisableTLSKey))
s3Flag[storage.DoNotVerifyTLS] = str.First(
s3Flag[storage.DoNotVerifyTLS],
os.Getenv(storage.DisableTLSVerificationKey))
return s3Flag
}