<!-- PR description--> New commands to initialize & connect to a repo on local or network attached storage. * `repo init filesystem --path /tmp/repo` * `repo connect filesystem --path /tmp/repo` Includes basic unit & e2e tests. More coverage to be added in a following PR to keep the size contained. **Updates:** * Added Repo path sanitization i.e. handle relative paths, make paths cross platform compatible, etc. * Removed retention artifacts, not supported for filesystem storage. * cli docs - auto updated. * Manually tested with all corso backup/restore/export commands. **Doesn't include** 1. Symlinks 2. User ids wiring into repo. 3. Repos documentation update - in an upcoming PR. 4. Prefix support -> kopia doesn't support prefixes for `filesystem` storage 5. More E2E tests. --- #### 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 - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #1416 #### Test Plan <!-- How will this be tested prior to merging.--> - [x] 💪 Manual - [x] ⚡ Unit test - [x] 💚 E2E
176 lines
4.1 KiB
Go
176 lines
4.1 KiB
Go
package storage
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/alcionai/clues"
|
|
"github.com/spf13/cast"
|
|
|
|
"github.com/alcionai/corso/src/internal/common"
|
|
)
|
|
|
|
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 {
|
|
return clues.New("value of " + k + " (" + v + ") does not match corso configuration value (" + vv + ")")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|