corso/src/pkg/storage/storage.go
Keepers 5258ef0f36
assert correct error on s3 conn bad configs e2e (#4387)
#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🐛 Bugfix
- [x] 🤖 Supportability/Tests

#### Test Plan

- [x] 💚 E2E
2023-09-29 00:45:16 +00:00

179 lines
4.2 KiB
Go

package storage
import (
"fmt"
"github.com/alcionai/clues"
"github.com/spf13/cast"
"github.com/alcionai/corso/src/internal/common"
)
var ErrVerifyingConfigStorage = clues.New("verifying configs in corso config file")
type ProviderType int
//go:generate stringer -type=ProviderType -linecomment
const (
ProviderUnknown ProviderType = 0 // Unknown Provider
ProviderS3 ProviderType = 1 // S3
ProviderFilesystem ProviderType = 2 // Filesystem
)
var StringToProviderType = map[string]ProviderType{
ProviderUnknown.String(): ProviderUnknown,
ProviderS3.String(): ProviderS3,
ProviderFilesystem.String(): ProviderFilesystem,
}
const (
StorageProviderTypeKey = "provider"
)
// storage parsing errors
var (
errMissingRequired = clues.New("missing required storage configuration")
)
// Storage defines a storage provider, along with any configuration
// required to set up or communicate with that provider.
type Storage struct {
Provider ProviderType
Config map[string]string
// TODO: These are AWS S3 specific -> move these out
SessionTags map[string]string
Role string
SessionName string
SessionDuration string
}
// NewStorage aggregates all the supplied configurations into a single configuration.
func NewStorage(p ProviderType, cfgs ...common.StringConfigurer) (Storage, error) {
cs, err := common.UnionStringConfigs(cfgs...)
return Storage{
Provider: p,
Config: cs,
}, err
}
// NewStorageUsingRole supports specifying an AWS IAM role the storage provider
// should assume.
func NewStorageUsingRole(
p ProviderType,
roleARN string,
sessionName string,
sessionTags map[string]string,
duration string,
cfgs ...common.StringConfigurer,
) (Storage, error) {
cs, err := common.UnionStringConfigs(cfgs...)
return Storage{
Provider: p,
Config: cs,
Role: roleARN,
SessionTags: sessionTags,
SessionName: sessionName,
SessionDuration: duration,
}, 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)
}
func (s Storage) StorageConfig() (Configurer, error) {
switch s.Provider {
case ProviderS3:
return buildS3ConfigFromMap(s.Config)
case ProviderFilesystem:
return buildFilesystemConfigFromMap(s.Config)
}
return nil, clues.New("unsupported storage provider: [" + s.Provider.String() + "]")
}
func NewStorageConfig(provider ProviderType) (Configurer, error) {
switch provider {
case ProviderS3:
return &S3Config{}, nil
case ProviderFilesystem:
return &FilesystemConfig{}, nil
}
return nil, clues.New("unsupported storage provider: [" + provider.String() + "]")
}
type Getter interface {
Get(key string) any
}
type Setter interface {
Set(key string, value any)
}
// WriteConfigToStorer writes config key value pairs to provided store.
type WriteConfigToStorer interface {
WriteConfigToStore(
s Setter,
)
}
type Configurer interface {
common.StringConfigurer
// ApplyOverrides fetches config from file, processes overrides
// from sources like environment variables and flags, and updates the
// underlying configuration accordingly.
ApplyConfigOverrides(
g Getter,
readConfigFromStore bool,
matchFromConfig bool,
overrides map[string]string,
) error
WriteConfigToStorer
}
// mustMatchConfig compares the values of each key to their config file value in store.
// If any value differs from the store value, an error is returned.
// values in m that aren't stored in the config are ignored.
func mustMatchConfig(
g Getter,
tomlMap map[string]string,
m map[string]string,
) error {
for k, v := range m {
if len(v) == 0 {
continue // empty variables will get caught by configuration validators, if necessary
}
tomlK, ok := tomlMap[k]
if !ok {
continue // m may declare values which aren't stored in the config file
}
vv := cast.ToString(g.Get(tomlK))
if v != vv {
err := clues.New("value of " + k + " (" + v + ") does not match corso configuration value (" + vv + ")")
return clues.Stack(ErrVerifyingConfigStorage, err)
}
}
return nil
}