mirror of
https://github.com/octoleo/restic.git
synced 2024-11-10 15:21:03 +00:00
119 lines
3.5 KiB
Go
119 lines
3.5 KiB
Go
package s3
|
|
|
|
import (
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/restic/restic/internal/errors"
|
|
"github.com/restic/restic/internal/options"
|
|
)
|
|
|
|
// Config contains all configuration necessary to connect to an s3 compatible
|
|
// server.
|
|
type Config struct {
|
|
Endpoint string
|
|
UseHTTP bool
|
|
KeyID string
|
|
Secret options.SecretString
|
|
Bucket string
|
|
Prefix string
|
|
Layout string `option:"layout" help:"use this backend layout (default: auto-detect)"`
|
|
StorageClass string `option:"storage-class" help:"set S3 storage class (STANDARD, STANDARD_IA, ONEZONE_IA, INTELLIGENT_TIERING or REDUCED_REDUNDANCY)"`
|
|
|
|
Connections uint `option:"connections" help:"set a limit for the number of concurrent connections (default: 5)"`
|
|
MaxRetries uint `option:"retries" help:"set the number of retries attempted"`
|
|
Region string `option:"region" help:"set region"`
|
|
BucketLookup string `option:"bucket-lookup" help:"bucket lookup style: 'auto', 'dns', or 'path'"`
|
|
ListObjectsV1 bool `option:"list-objects-v1" help:"use deprecated V1 api for ListObjects calls"`
|
|
}
|
|
|
|
// NewConfig returns a new Config with the default values filled in.
|
|
func NewConfig() Config {
|
|
return Config{
|
|
Connections: 5,
|
|
ListObjectsV1: false,
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
options.Register("s3", Config{})
|
|
}
|
|
|
|
// ParseConfig parses the string s and extracts the s3 config. The two
|
|
// supported configuration formats are s3://host/bucketname/prefix and
|
|
// s3:host/bucketname/prefix. The host can also be a valid s3 region
|
|
// name. If no prefix is given the prefix "restic" will be used.
|
|
func ParseConfig(s string) (Config, error) {
|
|
switch {
|
|
case strings.HasPrefix(s, "s3:http"):
|
|
// assume that a URL has been specified, parse it and
|
|
// use the host as the endpoint and the path as the
|
|
// bucket name and prefix
|
|
url, err := url.Parse(s[3:])
|
|
if err != nil {
|
|
return Config{}, errors.WithStack(err)
|
|
}
|
|
|
|
if url.Path == "" {
|
|
return Config{}, errors.New("s3: bucket name not found")
|
|
}
|
|
|
|
bucket, path, _ := strings.Cut(url.Path[1:], "/")
|
|
return createConfig(url.Host, bucket, path, url.Scheme == "http")
|
|
case strings.HasPrefix(s, "s3://"):
|
|
s = s[5:]
|
|
case strings.HasPrefix(s, "s3:"):
|
|
s = s[3:]
|
|
default:
|
|
return Config{}, errors.New("s3: invalid format")
|
|
}
|
|
// use the first entry of the path as the endpoint and the
|
|
// remainder as bucket name and prefix
|
|
endpoint, rest, _ := strings.Cut(s, "/")
|
|
bucket, prefix, _ := strings.Cut(rest, "/")
|
|
return createConfig(endpoint, bucket, prefix, false)
|
|
}
|
|
|
|
func createConfig(endpoint, bucket, prefix string, useHTTP bool) (Config, error) {
|
|
if endpoint == "" {
|
|
return Config{}, errors.New("s3: invalid format, host/region or bucket name not found")
|
|
}
|
|
|
|
if prefix != "" {
|
|
prefix = path.Clean(prefix)
|
|
}
|
|
|
|
cfg := NewConfig()
|
|
cfg.Endpoint = endpoint
|
|
cfg.UseHTTP = useHTTP
|
|
cfg.Bucket = bucket
|
|
cfg.Prefix = prefix
|
|
return cfg, nil
|
|
}
|
|
|
|
// ApplyEnvironment saves values from the environment to the config.
|
|
func ApplyEnvironment(cfgRaw interface{}) error {
|
|
cfg := cfgRaw.(*Config)
|
|
if cfg.KeyID == "" {
|
|
cfg.KeyID = os.Getenv("AWS_ACCESS_KEY_ID")
|
|
}
|
|
|
|
if cfg.Secret.String() == "" {
|
|
cfg.Secret = options.NewSecretString(os.Getenv("AWS_SECRET_ACCESS_KEY"))
|
|
}
|
|
|
|
if cfg.KeyID == "" && cfg.Secret.String() != "" {
|
|
return errors.Fatalf("unable to open S3 backend: Key ID ($AWS_ACCESS_KEY_ID) is empty")
|
|
} else if cfg.KeyID != "" && cfg.Secret.String() == "" {
|
|
return errors.Fatalf("unable to open S3 backend: Secret ($AWS_SECRET_ACCESS_KEY) is empty")
|
|
}
|
|
|
|
if cfg.Region == "" {
|
|
cfg.Region = os.Getenv("AWS_DEFAULT_REGION")
|
|
}
|
|
|
|
return nil
|
|
}
|