Compare commits

...

1 Commits

Author SHA1 Message Date
Abhishek Pandey
711245684d Move config code around, introduce interface for storageconfiguration 2023-09-05 16:44:40 +05:30
10 changed files with 390 additions and 269 deletions

View File

@ -203,14 +203,14 @@ func Read(ctx context.Context) error {
// It does not check for conflicts or existing data. // It does not check for conflicts or existing data.
func WriteRepoConfig( func WriteRepoConfig(
ctx context.Context, ctx context.Context,
s3Config storage.S3Config, storageConfig StorageConfigurer,
m365Config account.M365Config, m365Config account.M365Config,
repoOpts repository.Options, repoOpts repository.Options,
repoID string, repoID string,
) error { ) error {
return writeRepoConfigWithViper( return writeRepoConfigWithViper(
GetViper(ctx), GetViper(ctx),
s3Config, storageConfig,
m365Config, m365Config,
repoOpts, repoOpts,
repoID) repoID)
@ -220,20 +220,16 @@ func WriteRepoConfig(
// struct for testing. // struct for testing.
func writeRepoConfigWithViper( func writeRepoConfigWithViper(
vpr *viper.Viper, vpr *viper.Viper,
s3Config storage.S3Config, storageConfig StorageConfigurer,
m365Config account.M365Config, m365Config account.M365Config,
repoOpts repository.Options, repoOpts repository.Options,
repoID string, repoID string,
) error { ) error {
s3Config = s3Config.Normalize() storageConfig.WriteConfigToViper(vpr)
// Rudimentary support for persisting repo config // Rudimentary support for persisting repo config
// TODO: Handle conflicts, support other config types // TODO: Handle conflicts, support other config types
vpr.Set(StorageProviderTypeKey, storage.ProviderS3.String())
vpr.Set(BucketNameKey, s3Config.Bucket)
vpr.Set(EndpointKey, s3Config.Endpoint)
vpr.Set(PrefixKey, s3Config.Prefix)
vpr.Set(DisableTLSKey, s3Config.DoNotUseTLS)
vpr.Set(DisableTLSVerificationKey, s3Config.DoNotVerifyTLS)
vpr.Set(RepoID, repoID) vpr.Set(RepoID, repoID)
// Need if-checks as Viper will write empty values otherwise. // Need if-checks as Viper will write empty values otherwise.
@ -335,12 +331,13 @@ func getUserHost(vpr *viper.Viper, readConfigFromViper bool) (string, string) {
// Helper funcs // Helper funcs
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// TODO: This is not really needed?
var constToTomlKeyMap = map[string]string{ var constToTomlKeyMap = map[string]string{
account.AzureTenantID: AzureTenantIDKey, account.AzureTenantID: AzureTenantIDKey,
AccountProviderTypeKey: AccountProviderTypeKey, AccountProviderTypeKey: AccountProviderTypeKey,
storage.Bucket: BucketNameKey, Bucket: BucketNameKey,
storage.Endpoint: EndpointKey, Endpoint: EndpointKey,
storage.Prefix: PrefixKey, Prefix: PrefixKey,
StorageProviderTypeKey: StorageProviderTypeKey, StorageProviderTypeKey: StorageProviderTypeKey,
} }

263
src/cli/config/s3config.go Normal file
View File

@ -0,0 +1,263 @@
package config
import (
"strconv"
"github.com/alcionai/clues"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/spf13/viper"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/common/str"
"github.com/alcionai/corso/src/pkg/credentials"
"github.com/alcionai/corso/src/pkg/storage"
)
// prerequisite: readRepoConfig must have been run prior to this to populate the global viper values.
func s3ConfigsFromViper(vpr *viper.Viper) (S3Config, error) {
var s3Config S3Config
s3Config.Bucket = vpr.GetString(BucketNameKey)
s3Config.Endpoint = vpr.GetString(EndpointKey)
s3Config.Prefix = vpr.GetString(PrefixKey)
s3Config.DoNotUseTLS = vpr.GetBool(DisableTLSKey)
s3Config.DoNotVerifyTLS = vpr.GetBool(DisableTLSVerificationKey)
return s3Config, nil
}
// prerequisite: readRepoConfig must have been run prior to this to populate the global viper values.
func s3CredsFromViper(vpr *viper.Viper, s3Config S3Config) (S3Config, error) {
s3Config.AccessKey = vpr.GetString(AccessKey)
s3Config.SecretKey = vpr.GetString(SecretAccessKey)
s3Config.SessionToken = vpr.GetString(SessionToken)
return s3Config, nil
}
func s3Overrides(in map[string]string) map[string]string {
return map[string]string{
Bucket: in[Bucket],
Endpoint: in[Endpoint],
Prefix: in[Prefix],
DoNotUseTLS: in[DoNotUseTLS],
DoNotVerifyTLS: in[DoNotVerifyTLS],
StorageProviderTypeKey: in[StorageProviderTypeKey],
}
}
type StorageConfigurer interface {
common.StringConfigurer
//Normalize() StorageConfigurer
WriteConfigToViper(vpr *viper.Viper)
}
// func NewStorageConfig(
// provider storage.StorageProvider,
// ) (StorageConfigurer, error) {
// switch provider {
// case storage.ProviderS3:
// return S3Config{}, nil
// default:
// return nil, clues.New("unsupported storage type")
// }
// }
// Hydrate from a config map
func NewStorageConfigFrom(s storage.Storage) (StorageConfigurer, error) {
switch s.Provider {
case storage.ProviderS3:
return makeS3Config(s.Config)
default:
return nil, clues.New("unsupported storage type")
}
}
type S3Config struct {
credentials.AWS
Bucket string // required
Endpoint string
Prefix string
DoNotUseTLS bool
DoNotVerifyTLS bool
}
// MakeS3Config retrieves the S3Config details from the Storage config.
func makeS3Config(config map[string]string) (StorageConfigurer, error) {
c := S3Config{}
if len(config) > 0 {
c.AccessKey = orEmptyString(config[keyS3AccessKey])
c.SecretKey = orEmptyString(config[keyS3SecretKey])
c.SessionToken = orEmptyString(config[keyS3SessionToken])
c.Bucket = orEmptyString(config[keyS3Bucket])
c.Endpoint = orEmptyString(config[keyS3Endpoint])
c.Prefix = orEmptyString(config[keyS3Prefix])
c.DoNotUseTLS = str.ParseBool(config[keyS3DoNotUseTLS])
c.DoNotVerifyTLS = str.ParseBool(config[keyS3DoNotVerifyTLS])
}
return c, c.validate()
}
// TODO: Remove storageconfigurer return value.
// Have it contained in current s3config.
func fetchS3ConfigFromViper(
vpr *viper.Viper,
readConfigFromViper bool,
matchFromConfig bool,
overrides map[string]string,
) (StorageConfigurer, error) {
var (
s3Cfg S3Config
err error
)
if readConfigFromViper {
if s3Cfg, err = s3ConfigsFromViper(vpr); err != nil {
clues.Wrap(err, "reading s3 configs from corso config file")
}
if b, ok := overrides[Bucket]; ok {
overrides[Bucket] = common.NormalizeBucket(b)
}
if p, ok := overrides[Prefix]; ok {
overrides[Prefix] = common.NormalizePrefix(p)
}
if matchFromConfig {
providerType := vpr.GetString(StorageProviderTypeKey)
if providerType != storage.ProviderS3.String() {
return nil, clues.New("unsupported storage provider" + providerType)
}
if err := mustMatchConfig(vpr, s3Overrides(overrides)); err != nil {
return nil, clues.Wrap(err, "verifying s3 configs in corso config file")
}
}
}
if s3Cfg, err = s3CredsFromViper(vpr, s3Cfg); err != nil {
return nil, clues.Wrap(err, "reading s3 configs from corso config file")
}
s3Overrides(overrides)
aws := credentials.GetAWS(overrides)
if len(aws.AccessKey) <= 0 || len(aws.SecretKey) <= 0 {
_, err = defaults.CredChain(defaults.Config().WithCredentialsChainVerboseErrors(true), defaults.Handlers()).Get()
if err != nil && (len(s3Cfg.AccessKey) > 0 || len(s3Cfg.SecretKey) > 0) {
aws = credentials.AWS{
AccessKey: s3Cfg.AccessKey,
SecretKey: s3Cfg.SecretKey,
SessionToken: s3Cfg.SessionToken,
}
err = nil
}
if err != nil {
return nil, clues.Wrap(err, "validating aws credentials")
}
}
s3Cfg = S3Config{
AWS: aws,
Bucket: str.First(overrides[Bucket], s3Cfg.Bucket),
Endpoint: str.First(overrides[Endpoint], s3Cfg.Endpoint, "s3.amazonaws.com"),
Prefix: str.First(overrides[Prefix], s3Cfg.Prefix),
DoNotUseTLS: str.ParseBool(str.First(
overrides[DoNotUseTLS],
strconv.FormatBool(s3Cfg.DoNotUseTLS),
"false",
)),
DoNotVerifyTLS: str.ParseBool(str.First(
overrides[DoNotVerifyTLS],
strconv.FormatBool(s3Cfg.DoNotVerifyTLS),
"false",
)),
}
return s3Cfg, s3Cfg.validate()
}
// config key consts
const (
keyS3AccessKey = "s3_access_key"
keyS3Bucket = "s3_bucket"
keyS3Endpoint = "s3_endpoint"
keyS3Prefix = "s3_prefix"
keyS3SecretKey = "s3_secret_key"
keyS3SessionToken = "s3_session_token"
keyS3DoNotUseTLS = "s3_donotusetls"
keyS3DoNotVerifyTLS = "s3_donotverifytls"
)
// config exported name consts
// TODO: Move these to storage?
const (
Bucket = "bucket"
Endpoint = "endpoint"
Prefix = "prefix"
DoNotUseTLS = "donotusetls"
DoNotVerifyTLS = "donotverifytls"
)
func normalize(c S3Config) S3Config {
return S3Config{
Bucket: common.NormalizeBucket(c.Bucket),
Endpoint: c.Endpoint,
Prefix: common.NormalizePrefix(c.Prefix),
DoNotUseTLS: c.DoNotUseTLS,
DoNotVerifyTLS: c.DoNotVerifyTLS,
}
}
// StringConfig transforms a s3Config struct into a plain
// map[string]string. All values in the original struct which
// serialize into the map are expected to be strings.
func (c S3Config) StringConfig() (map[string]string, error) {
cn := normalize(c)
cfg := map[string]string{
keyS3AccessKey: c.AccessKey,
keyS3Bucket: cn.Bucket,
keyS3Endpoint: cn.Endpoint,
keyS3Prefix: cn.Prefix,
keyS3SecretKey: c.SecretKey,
keyS3SessionToken: c.SessionToken,
keyS3DoNotUseTLS: strconv.FormatBool(cn.DoNotUseTLS),
keyS3DoNotVerifyTLS: strconv.FormatBool(cn.DoNotVerifyTLS),
}
return cfg, c.validate()
}
func (c S3Config) WriteConfigToViper(vpr *viper.Viper) {
cn := normalize(c)
vpr.Set(StorageProviderTypeKey, storage.ProviderS3.String())
vpr.Set(BucketNameKey, cn.Bucket)
vpr.Set(EndpointKey, cn.Endpoint)
vpr.Set(PrefixKey, cn.Prefix)
vpr.Set(DisableTLSKey, cn.DoNotUseTLS)
vpr.Set(DisableTLSVerificationKey, cn.DoNotVerifyTLS)
}
// storage parsing errors
var (
errMissingRequired = clues.New("missing required storage configuration")
)
func (c S3Config) validate() error {
check := map[string]string{
Bucket: c.Bucket,
}
for k, v := range check {
if len(v) == 0 {
return clues.Stack(errMissingRequired, clues.New(k))
}
}
return nil
}

View File

@ -1,54 +1,19 @@
package config package config
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/alcionai/corso/src/cli/flags" "github.com/alcionai/corso/src/cli/flags"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/common/str" "github.com/alcionai/corso/src/internal/common/str"
"github.com/alcionai/corso/src/pkg/credentials" "github.com/alcionai/corso/src/pkg/credentials"
"github.com/alcionai/corso/src/pkg/storage" "github.com/alcionai/corso/src/pkg/storage"
) )
// prerequisite: readRepoConfig must have been run prior to this to populate the global viper values.
func s3ConfigsFromViper(vpr *viper.Viper) (storage.S3Config, error) {
var s3Config storage.S3Config
s3Config.Bucket = vpr.GetString(BucketNameKey)
s3Config.Endpoint = vpr.GetString(EndpointKey)
s3Config.Prefix = vpr.GetString(PrefixKey)
s3Config.DoNotUseTLS = vpr.GetBool(DisableTLSKey)
s3Config.DoNotVerifyTLS = vpr.GetBool(DisableTLSVerificationKey)
return s3Config, nil
}
// prerequisite: readRepoConfig must have been run prior to this to populate the global viper values.
func s3CredsFromViper(vpr *viper.Viper, s3Config storage.S3Config) (storage.S3Config, error) {
s3Config.AccessKey = vpr.GetString(AccessKey)
s3Config.SecretKey = vpr.GetString(SecretAccessKey)
s3Config.SessionToken = vpr.GetString(SessionToken)
return s3Config, nil
}
func s3Overrides(in map[string]string) map[string]string {
return map[string]string{
storage.Bucket: in[storage.Bucket],
storage.Endpoint: in[storage.Endpoint],
storage.Prefix: in[storage.Prefix],
storage.DoNotUseTLS: in[storage.DoNotUseTLS],
storage.DoNotVerifyTLS: in[storage.DoNotVerifyTLS],
StorageProviderTypeKey: in[StorageProviderTypeKey],
}
}
// configureStorage builds a complete storage configuration from a mix of // configureStorage builds a complete storage configuration from a mix of
// viper properties and manual overrides. // viper properties and manual overrides.
func configureStorage( func configureStorage(
@ -58,74 +23,19 @@ func configureStorage(
overrides map[string]string, overrides map[string]string,
) (storage.Storage, error) { ) (storage.Storage, error) {
var ( var (
s3Cfg storage.S3Config store storage.Storage
store storage.Storage err error
err error provider = storage.ProviderS3 // temporary
) )
if readConfigFromViper { storageCfg, err := fetchStorageConfigFromViper(
if s3Cfg, err = s3ConfigsFromViper(vpr); err != nil { vpr,
return store, clues.Wrap(err, "reading s3 configs from corso config file") provider, // Make it generic
} readConfigFromViper,
matchFromConfig,
if b, ok := overrides[storage.Bucket]; ok { overrides)
overrides[storage.Bucket] = common.NormalizeBucket(b) if err != nil {
} return store, err
if p, ok := overrides[storage.Prefix]; ok {
overrides[storage.Prefix] = common.NormalizePrefix(p)
}
if matchFromConfig {
providerType := vpr.GetString(StorageProviderTypeKey)
if providerType != storage.ProviderS3.String() {
return store, clues.New("unsupported storage provider: " + providerType)
}
if err := mustMatchConfig(vpr, s3Overrides(overrides)); err != nil {
return store, clues.Wrap(err, "verifying s3 configs in corso config file")
}
}
}
if s3Cfg, err = s3CredsFromViper(vpr, s3Cfg); err != nil {
return store, clues.Wrap(err, "reading s3 configs from corso config file")
}
s3Overrides(overrides)
aws := credentials.GetAWS(overrides)
if len(aws.AccessKey) <= 0 || len(aws.SecretKey) <= 0 {
_, err = defaults.CredChain(defaults.Config().WithCredentialsChainVerboseErrors(true), defaults.Handlers()).Get()
if err != nil && (len(s3Cfg.AccessKey) > 0 || len(s3Cfg.SecretKey) > 0) {
aws = credentials.AWS{
AccessKey: s3Cfg.AccessKey,
SecretKey: s3Cfg.SecretKey,
SessionToken: s3Cfg.SessionToken,
}
err = nil
}
if err != nil {
return store, clues.Wrap(err, "validating aws credentials")
}
}
s3Cfg = storage.S3Config{
AWS: aws,
Bucket: str.First(overrides[storage.Bucket], s3Cfg.Bucket),
Endpoint: str.First(overrides[storage.Endpoint], s3Cfg.Endpoint, "s3.amazonaws.com"),
Prefix: str.First(overrides[storage.Prefix], s3Cfg.Prefix),
DoNotUseTLS: str.ParseBool(str.First(
overrides[storage.DoNotUseTLS],
strconv.FormatBool(s3Cfg.DoNotUseTLS),
"false",
)),
DoNotVerifyTLS: str.ParseBool(str.First(
overrides[storage.DoNotVerifyTLS],
strconv.FormatBool(s3Cfg.DoNotVerifyTLS),
"false",
)),
} }
// compose the common config and credentials // compose the common config and credentials
@ -147,14 +57,13 @@ func configureStorage(
// ensure required properties are present // ensure required properties are present
if err := requireProps(map[string]string{ if err := requireProps(map[string]string{
storage.Bucket: s3Cfg.Bucket,
credentials.CorsoPassphrase: corso.CorsoPassphrase, credentials.CorsoPassphrase: corso.CorsoPassphrase,
}); err != nil { }); err != nil {
return storage.Storage{}, err return storage.Storage{}, err
} }
// build the storage // build the storage
store, err = storage.NewStorage(storage.ProviderS3, s3Cfg, cCfg) store, err = storage.NewStorage(provider, storageCfg, cCfg)
if err != nil { if err != nil {
return store, clues.Wrap(err, "configuring repository storage") return store, clues.Wrap(err, "configuring repository storage")
} }
@ -162,6 +71,25 @@ func configureStorage(
return store, nil return store, nil
} }
func fetchStorageConfigFromViper(
vpr *viper.Viper,
p storage.StorageProvider,
readConfigFromViper bool,
matchFromConfig bool,
overrides map[string]string,
) (StorageConfigurer, error) {
switch p {
case storage.ProviderS3:
return fetchS3ConfigFromViper(
vpr,
readConfigFromViper,
matchFromConfig,
overrides)
}
return nil, clues.New("unsupported storage provider")
}
// GetCorso is a helper for aggregating Corso secrets and credentials. // GetCorso is a helper for aggregating Corso secrets and credentials.
func GetAndInsertCorso(passphase string) credentials.Corso { func GetAndInsertCorso(passphase string) credentials.Corso {
// fetch data from flag, env var or func param giving priority to func param // fetch data from flag, env var or func param giving priority to func param
@ -172,3 +100,20 @@ func GetAndInsertCorso(passphase string) credentials.Corso {
CorsoPassphrase: corsoPassph, CorsoPassphrase: corsoPassph,
} }
} }
// Helper for parsing the values in a config object.
// If the value is nil or not a string, returns an empty string.
func orEmptyString(v any) string {
defer func() {
r := recover()
if r != nil {
fmt.Printf("panic recovery casting %v to string\n", v)
}
}()
if v == nil {
return ""
}
return v.(string)
}

View File

@ -128,6 +128,7 @@ func initS3Cmd(cmd *cobra.Command, args []string) error {
// s3 values from flags // s3 values from flags
s3Override := S3Overrides(cmd) s3Override := S3Overrides(cmd)
// Need to send provider here
cfg, err := config.GetConfigRepoDetails(ctx, true, false, s3Override) cfg, err := config.GetConfigRepoDetails(ctx, true, false, s3Override)
if err != nil { if err != nil {
return Only(ctx, err) return Only(ctx, err)
@ -149,11 +150,17 @@ func initS3Cmd(cmd *cobra.Command, args []string) error {
cfg.Account.ID(), cfg.Account.ID(),
opt) opt)
s3Cfg, err := cfg.Storage.S3Config() storageCfg, err := config.NewStorageConfigFrom(cfg.Storage)
if err != nil { if err != nil {
return Only(ctx, clues.Wrap(err, "Retrieving s3 configuration")) return Only(ctx, clues.Wrap(err, "Retrieving s3 configuration"))
} }
s3Cfg, ok := storageCfg.(config.S3Config)
if !ok {
return Only(ctx, clues.New("Casting storage config to S3Config"))
}
// TODO: move this to cfg validate
if strings.HasPrefix(s3Cfg.Endpoint, "http://") || strings.HasPrefix(s3Cfg.Endpoint, "https://") { if strings.HasPrefix(s3Cfg.Endpoint, "http://") || strings.HasPrefix(s3Cfg.Endpoint, "https://") {
invalidEndpointErr := "endpoint doesn't support specifying protocol. " + invalidEndpointErr := "endpoint doesn't support specifying protocol. " +
"pass --disable-tls flag to use http:// instead of default https://" "pass --disable-tls flag to use http:// instead of default https://"
@ -214,6 +221,7 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error {
// s3 values from flags // s3 values from flags
s3Override := S3Overrides(cmd) s3Override := S3Overrides(cmd)
// Send provider here
cfg, err := config.GetConfigRepoDetails(ctx, true, true, s3Override) cfg, err := config.GetConfigRepoDetails(ctx, true, true, s3Override)
if err != nil { if err != nil {
return Only(ctx, err) return Only(ctx, err)
@ -224,11 +232,16 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error {
repoID = events.RepoIDNotFound repoID = events.RepoIDNotFound
} }
s3Cfg, err := cfg.Storage.S3Config() storageCfg, err := config.NewStorageConfigFrom(cfg.Storage)
if err != nil { if err != nil {
return Only(ctx, clues.Wrap(err, "Retrieving s3 configuration")) return Only(ctx, clues.Wrap(err, "Retrieving s3 configuration"))
} }
s3Cfg, ok := storageCfg.(config.S3Config)
if !ok {
return Only(ctx, clues.New("Casting storage config to S3Config"))
}
m365, err := cfg.Account.M365Config() m365, err := cfg.Account.M365Config()
if err != nil { if err != nil {
return Only(ctx, clues.Wrap(err, "Failed to parse m365 account config")) return Only(ctx, clues.Wrap(err, "Failed to parse m365 account config"))
@ -287,23 +300,23 @@ func PopulateS3Flags(flagset flags.PopulatedFlags) map[string]string {
} }
if _, ok := flagset[bucketFN]; ok { if _, ok := flagset[bucketFN]; ok {
s3Overrides[storage.Bucket] = bucket s3Overrides[config.Bucket] = bucket
} }
if _, ok := flagset[prefixFN]; ok { if _, ok := flagset[prefixFN]; ok {
s3Overrides[storage.Prefix] = prefix s3Overrides[config.Prefix] = prefix
} }
if _, ok := flagset[doNotUseTLSFN]; ok { if _, ok := flagset[doNotUseTLSFN]; ok {
s3Overrides[storage.DoNotUseTLS] = strconv.FormatBool(doNotUseTLS) s3Overrides[config.DoNotUseTLS] = strconv.FormatBool(doNotUseTLS)
} }
if _, ok := flagset[doNotVerifyTLSFN]; ok { if _, ok := flagset[doNotVerifyTLSFN]; ok {
s3Overrides[storage.DoNotVerifyTLS] = strconv.FormatBool(doNotVerifyTLS) s3Overrides[config.DoNotVerifyTLS] = strconv.FormatBool(doNotVerifyTLS)
} }
if _, ok := flagset[endpointFN]; ok { if _, ok := flagset[endpointFN]; ok {
s3Overrides[storage.Endpoint] = endpoint s3Overrides[config.Endpoint] = endpoint
} }
return s3Overrides return s3Overrides

View File

@ -63,7 +63,7 @@ func AccountConnectAndWriteRepoConfig(
return nil, nil, err return nil, nil, err
} }
s3Config, err := stg.S3Config() storageConfig, err := config.NewStorageConfigFrom(*stg)
if err != nil { if err != nil {
logger.CtxErr(ctx, err).Info("getting storage configuration") logger.CtxErr(ctx, err).Info("getting storage configuration")
return nil, nil, err return nil, nil, err
@ -77,7 +77,7 @@ func AccountConnectAndWriteRepoConfig(
// repo config gets set during repo connect and init. // repo config gets set during repo connect and init.
// This call confirms we have the correct values. // This call confirms we have the correct values.
err = config.WriteRepoConfig(ctx, s3Config, m365Config, opts.Repo, r.GetID()) err = config.WriteRepoConfig(ctx, storageConfig, m365Config, opts.Repo, r.GetID())
if err != nil { if err != nil {
logger.CtxErr(ctx, err).Info("writing to repository configuration") logger.CtxErr(ctx, err).Info("writing to repository configuration")
return nil, nil, err return nil, nil, err

View File

@ -7,6 +7,7 @@ import (
"github.com/kopia/kopia/repo/blob" "github.com/kopia/kopia/repo/blob"
"github.com/kopia/kopia/repo/blob/s3" "github.com/kopia/kopia/repo/blob/s3"
"github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/control/repository"
"github.com/alcionai/corso/src/pkg/storage" "github.com/alcionai/corso/src/pkg/storage"
) )
@ -20,11 +21,16 @@ func s3BlobStorage(
repoOpts repository.Options, repoOpts repository.Options,
s storage.Storage, s storage.Storage,
) (blob.Storage, error) { ) (blob.Storage, error) {
cfg, err := s.S3Config() sCfg, err := config.NewStorageConfigFrom(s)
if err != nil { if err != nil {
return nil, clues.Stack(err).WithClues(ctx) return nil, clues.Stack(err).WithClues(ctx)
} }
cfg, ok := sCfg.(config.S3Config)
if !ok {
return nil, clues.New("casting storage config to S3Config")
}
endpoint := defaultS3Endpoint endpoint := defaultS3Endpoint
if len(cfg.Endpoint) > 0 { if len(cfg.Endpoint) > 0 {
endpoint = cfg.Endpoint endpoint = cfg.Endpoint

View File

@ -1,11 +1,14 @@
package storage package storage
import ( import (
"fmt"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/alcionai/corso/src/pkg/credentials" "github.com/alcionai/corso/src/pkg/credentials"
) )
// Move this to config
type CommonConfig struct { type CommonConfig struct {
credentials.Corso // requires: CorsoPassphrase credentials.Corso // requires: CorsoPassphrase
@ -42,6 +45,11 @@ func (s Storage) CommonConfig() (CommonConfig, error) {
return c, c.validate() return c, c.validate()
} }
// storage parsing errors
var (
errMissingRequired = clues.New("missing required storage configuration")
)
// ensures all required properties are present // ensures all required properties are present
func (c CommonConfig) validate() error { func (c CommonConfig) validate() error {
if len(c.CorsoPassphrase) == 0 { if len(c.CorsoPassphrase) == 0 {
@ -51,3 +59,20 @@ func (c CommonConfig) validate() error {
// kopiaCfgFilePath is not required // kopiaCfgFilePath is not required
return nil return nil
} }
// Helper for parsing the values in a config object.
// If the value is nil or not a string, returns an empty string.
func orEmptyString(v any) string {
defer func() {
r := recover()
if r != nil {
fmt.Printf("panic recovery casting %v to string\n", v)
}
}()
if v == nil {
return ""
}
return v.(string)
}

View File

@ -1,102 +0,0 @@
package storage
import (
"strconv"
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/common/str"
"github.com/alcionai/corso/src/pkg/credentials"
)
type S3Config struct {
credentials.AWS
Bucket string // required
Endpoint string
Prefix string
DoNotUseTLS bool
DoNotVerifyTLS bool
}
// config key consts
const (
keyS3AccessKey = "s3_access_key"
keyS3Bucket = "s3_bucket"
keyS3Endpoint = "s3_endpoint"
keyS3Prefix = "s3_prefix"
keyS3SecretKey = "s3_secret_key"
keyS3SessionToken = "s3_session_token"
keyS3DoNotUseTLS = "s3_donotusetls"
keyS3DoNotVerifyTLS = "s3_donotverifytls"
)
// config exported name consts
const (
Bucket = "bucket"
Endpoint = "endpoint"
Prefix = "prefix"
DoNotUseTLS = "donotusetls"
DoNotVerifyTLS = "donotverifytls"
)
func (c S3Config) Normalize() S3Config {
return S3Config{
Bucket: common.NormalizeBucket(c.Bucket),
Endpoint: c.Endpoint,
Prefix: common.NormalizePrefix(c.Prefix),
DoNotUseTLS: c.DoNotUseTLS,
DoNotVerifyTLS: c.DoNotVerifyTLS,
}
}
// StringConfig transforms a s3Config struct into a plain
// map[string]string. All values in the original struct which
// serialize into the map are expected to be strings.
func (c S3Config) StringConfig() (map[string]string, error) {
cn := c.Normalize()
cfg := map[string]string{
keyS3AccessKey: c.AccessKey,
keyS3Bucket: cn.Bucket,
keyS3Endpoint: cn.Endpoint,
keyS3Prefix: cn.Prefix,
keyS3SecretKey: c.SecretKey,
keyS3SessionToken: c.SessionToken,
keyS3DoNotUseTLS: strconv.FormatBool(cn.DoNotUseTLS),
keyS3DoNotVerifyTLS: strconv.FormatBool(cn.DoNotVerifyTLS),
}
return cfg, c.validate()
}
// S3Config retrieves the S3Config details from the Storage config.
func (s Storage) S3Config() (S3Config, error) {
c := S3Config{}
if len(s.Config) > 0 {
c.AccessKey = orEmptyString(s.Config[keyS3AccessKey])
c.SecretKey = orEmptyString(s.Config[keyS3SecretKey])
c.SessionToken = orEmptyString(s.Config[keyS3SessionToken])
c.Bucket = orEmptyString(s.Config[keyS3Bucket])
c.Endpoint = orEmptyString(s.Config[keyS3Endpoint])
c.Prefix = orEmptyString(s.Config[keyS3Prefix])
c.DoNotUseTLS = str.ParseBool(s.Config[keyS3DoNotUseTLS])
c.DoNotVerifyTLS = str.ParseBool(s.Config[keyS3DoNotVerifyTLS])
}
return c, c.validate()
}
func (c S3Config) validate() error {
check := map[string]string{
Bucket: c.Bucket,
}
for k, v := range check {
if len(v) == 0 {
return clues.Stack(errMissingRequired, clues.New(k))
}
}
return nil
}

View File

@ -1,30 +1,21 @@
package storage package storage
import ( import (
"fmt"
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
) )
type storageProvider int type StorageProvider int
//go:generate stringer -type=storageProvider -linecomment //go:generate stringer -type=StorageProvider -linecomment
const ( const (
ProviderUnknown storageProvider = 0 // Unknown Provider ProviderUnknown StorageProvider = 0 // Unknown Provider
ProviderS3 storageProvider = 1 // S3 ProviderS3 StorageProvider = 1 // S3
)
// storage parsing errors
var (
errMissingRequired = clues.New("missing required storage configuration")
) )
// Storage defines a storage provider, along with any configuration // Storage defines a storage provider, along with any configuration
// required to set up or communicate with that provider. // required to set up or communicate with that provider.
type Storage struct { type Storage struct {
Provider storageProvider Provider StorageProvider
Config map[string]string Config map[string]string
// TODO: These are AWS S3 specific -> move these out // TODO: These are AWS S3 specific -> move these out
SessionTags map[string]string SessionTags map[string]string
@ -34,7 +25,7 @@ type Storage struct {
} }
// NewStorage aggregates all the supplied configurations into a single configuration. // NewStorage aggregates all the supplied configurations into a single configuration.
func NewStorage(p storageProvider, cfgs ...common.StringConfigurer) (Storage, error) { func NewStorage(p StorageProvider, cfgs ...common.StringConfigurer) (Storage, error) {
cs, err := common.UnionStringConfigs(cfgs...) cs, err := common.UnionStringConfigs(cfgs...)
return Storage{ return Storage{
@ -46,7 +37,7 @@ func NewStorage(p storageProvider, cfgs ...common.StringConfigurer) (Storage, er
// NewStorageUsingRole supports specifying an AWS IAM role the storage provider // NewStorageUsingRole supports specifying an AWS IAM role the storage provider
// should assume. // should assume.
func NewStorageUsingRole( func NewStorageUsingRole(
p storageProvider, p StorageProvider,
roleARN string, roleARN string,
sessionName string, sessionName string,
sessionTags map[string]string, sessionTags map[string]string,
@ -64,20 +55,3 @@ func NewStorageUsingRole(
SessionDuration: duration, SessionDuration: duration,
}, err }, err
} }
// Helper for parsing the values in a config object.
// If the value is nil or not a string, returns an empty string.
func orEmptyString(v any) string {
defer func() {
r := recover()
if r != nil {
fmt.Printf("panic recovery casting %v to string\n", v)
}
}()
if v == nil {
return ""
}
return v.(string)
}

View File

@ -1,4 +1,4 @@
// Code generated by "stringer -type=storageProvider -linecomment"; DO NOT EDIT. // Code generated by "stringer -type=StorageProvider -linecomment"; DO NOT EDIT.
package storage package storage
@ -12,13 +12,13 @@ func _() {
_ = x[ProviderS3-1] _ = x[ProviderS3-1]
} }
const _storageProvider_name = "Unknown ProviderS3" const _StorageProvider_name = "Unknown ProviderS3"
var _storageProvider_index = [...]uint8{0, 16, 18} var _StorageProvider_index = [...]uint8{0, 16, 18}
func (i storageProvider) String() string { func (i StorageProvider) String() string {
if i < 0 || i >= storageProvider(len(_storageProvider_index)-1) { if i < 0 || i >= StorageProvider(len(_StorageProvider_index)-1) {
return "storageProvider(" + strconv.FormatInt(int64(i), 10) + ")" return "StorageProvider(" + strconv.FormatInt(int64(i), 10) + ")"
} }
return _storageProvider_name[_storageProvider_index[i]:_storageProvider_index[i+1]] return _StorageProvider_name[_StorageProvider_index[i]:_StorageProvider_index[i+1]]
} }