2015-05-10 15:20:58 +00:00
|
|
|
package s3
|
|
|
|
|
|
|
|
import (
|
2017-06-03 15:39:57 +00:00
|
|
|
"context"
|
2020-01-03 19:37:43 +00:00
|
|
|
"fmt"
|
2020-12-19 11:39:48 +00:00
|
|
|
"hash"
|
2015-05-10 15:20:58 +00:00
|
|
|
"io"
|
2017-09-24 18:04:23 +00:00
|
|
|
"net/http"
|
2017-05-13 21:55:22 +00:00
|
|
|
"os"
|
2016-02-15 17:47:10 +00:00
|
|
|
"path"
|
2015-05-10 15:20:58 +00:00
|
|
|
"strings"
|
|
|
|
|
2023-10-01 09:40:12 +00:00
|
|
|
"github.com/restic/restic/internal/backend"
|
2022-10-15 14:23:39 +00:00
|
|
|
"github.com/restic/restic/internal/backend/layout"
|
2023-06-08 11:04:34 +00:00
|
|
|
"github.com/restic/restic/internal/backend/location"
|
2023-10-01 08:24:33 +00:00
|
|
|
"github.com/restic/restic/internal/backend/util"
|
2020-12-17 11:47:53 +00:00
|
|
|
"github.com/restic/restic/internal/debug"
|
2017-07-23 12:21:03 +00:00
|
|
|
"github.com/restic/restic/internal/errors"
|
2024-05-12 09:55:34 +00:00
|
|
|
"github.com/restic/restic/internal/feature"
|
2016-08-21 15:46:23 +00:00
|
|
|
|
2020-09-19 19:57:02 +00:00
|
|
|
"github.com/minio/minio-go/v7"
|
|
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
2015-05-10 15:20:58 +00:00
|
|
|
)
|
|
|
|
|
2017-06-07 19:59:01 +00:00
|
|
|
// Backend stores data on an S3 endpoint.
|
|
|
|
type Backend struct {
|
2017-06-15 14:41:09 +00:00
|
|
|
client *minio.Client
|
|
|
|
cfg Config
|
2022-10-15 14:23:39 +00:00
|
|
|
layout.Layout
|
2015-05-15 22:29:48 +00:00
|
|
|
}
|
|
|
|
|
2017-06-07 19:59:01 +00:00
|
|
|
// make sure that *Backend implements backend.Backend
|
2023-10-01 09:40:12 +00:00
|
|
|
var _ backend.Backend = &Backend{}
|
2017-06-03 15:39:57 +00:00
|
|
|
|
2023-06-08 11:04:34 +00:00
|
|
|
func NewFactory() location.Factory {
|
2023-06-08 15:32:43 +00:00
|
|
|
return location.NewHTTPBackendFactory("s3", ParseConfig, location.NoPassword, Create, Open)
|
2023-06-08 11:04:34 +00:00
|
|
|
}
|
|
|
|
|
2024-08-26 19:16:22 +00:00
|
|
|
func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("open, config %#v", cfg)
|
2015-05-10 15:20:58 +00:00
|
|
|
|
2023-06-08 13:28:07 +00:00
|
|
|
if cfg.KeyID == "" && cfg.Secret.String() != "" {
|
|
|
|
return nil, errors.Fatalf("unable to open S3 backend: Key ID ($AWS_ACCESS_KEY_ID) is empty")
|
|
|
|
} else if cfg.KeyID != "" && cfg.Secret.String() == "" {
|
|
|
|
return nil, errors.Fatalf("unable to open S3 backend: Secret ($AWS_SECRET_ACCESS_KEY) is empty")
|
|
|
|
}
|
|
|
|
|
2017-06-12 19:09:26 +00:00
|
|
|
if cfg.MaxRetries > 0 {
|
|
|
|
minio.MaxRetry = int(cfg.MaxRetries)
|
|
|
|
}
|
|
|
|
|
2024-07-07 09:32:40 +00:00
|
|
|
creds, err := getCredentials(cfg, rt)
|
2021-09-30 18:45:31 +00:00
|
|
|
if err != nil {
|
2023-09-18 18:09:32 +00:00
|
|
|
return nil, errors.Wrap(err, "s3.getCredentials")
|
2021-09-30 18:45:31 +00:00
|
|
|
}
|
|
|
|
|
2020-01-03 19:37:43 +00:00
|
|
|
options := &minio.Options{
|
2020-09-19 19:57:02 +00:00
|
|
|
Creds: creds,
|
|
|
|
Secure: !cfg.UseHTTP,
|
|
|
|
Region: cfg.Region,
|
|
|
|
Transport: rt,
|
2020-01-03 19:37:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch strings.ToLower(cfg.BucketLookup) {
|
|
|
|
case "", "auto":
|
|
|
|
options.BucketLookup = minio.BucketLookupAuto
|
|
|
|
case "dns":
|
|
|
|
options.BucketLookup = minio.BucketLookupDNS
|
|
|
|
case "path":
|
|
|
|
options.BucketLookup = minio.BucketLookupPath
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf(`bad bucket-lookup style %q must be "auto", "path" or "dns"`, cfg.BucketLookup)
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := minio.New(cfg.Endpoint, options)
|
2017-10-09 19:48:42 +00:00
|
|
|
if err != nil {
|
2020-09-19 19:57:02 +00:00
|
|
|
return nil, errors.Wrap(err, "minio.New")
|
2015-08-26 11:25:05 +00:00
|
|
|
}
|
|
|
|
|
2017-06-07 19:59:01 +00:00
|
|
|
be := &Backend{
|
2017-06-15 14:41:09 +00:00
|
|
|
client: client,
|
|
|
|
cfg: cfg,
|
2024-08-26 18:28:39 +00:00
|
|
|
Layout: layout.NewDefaultLayout(cfg.Prefix, path.Join),
|
2017-03-14 22:05:51 +00:00
|
|
|
}
|
2017-02-10 18:24:54 +00:00
|
|
|
|
2017-06-17 20:15:58 +00:00
|
|
|
return be, nil
|
|
|
|
}
|
|
|
|
|
2023-09-18 18:09:32 +00:00
|
|
|
// getCredentials -- runs through the various credential types and returns the first one that works.
|
|
|
|
// additionally if the user has specified a role to assume, it will do that as well.
|
2024-07-07 09:32:40 +00:00
|
|
|
func getCredentials(cfg Config, tr http.RoundTripper) (*credentials.Credentials, error) {
|
2024-07-08 17:42:00 +00:00
|
|
|
if cfg.UnsafeAnonymousAuth {
|
|
|
|
return credentials.New(&credentials.Static{}), nil
|
|
|
|
}
|
|
|
|
|
2023-09-18 18:09:32 +00:00
|
|
|
// Chains all credential types, in the following order:
|
|
|
|
// - Static credentials provided by user
|
|
|
|
// - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
|
|
|
|
// - Minio env vars (i.e. MINIO_ACCESS_KEY)
|
|
|
|
// - AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
|
|
|
|
// - Minio creds file (i.e. MINIO_SHARED_CREDENTIALS_FILE or ~/.mc/config.json)
|
|
|
|
// - IAM profile based credentials. (performs an HTTP
|
|
|
|
// call to a pre-defined endpoint, only valid inside
|
|
|
|
// configured ec2 instances)
|
|
|
|
creds := credentials.NewChainCredentials([]credentials.Provider{
|
2024-01-06 20:43:41 +00:00
|
|
|
&credentials.EnvAWS{},
|
2023-09-18 18:09:32 +00:00
|
|
|
&credentials.Static{
|
|
|
|
Value: credentials.Value{
|
|
|
|
AccessKeyID: cfg.KeyID,
|
|
|
|
SecretAccessKey: cfg.Secret.Unwrap(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&credentials.EnvMinio{},
|
|
|
|
&credentials.FileAWSCredentials{},
|
|
|
|
&credentials.FileMinioClient{},
|
|
|
|
&credentials.IAM{
|
|
|
|
Client: &http.Client{
|
2024-07-07 09:32:40 +00:00
|
|
|
Transport: tr,
|
2023-09-18 18:09:32 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
c, err := creds.Get()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "creds.Get")
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.SignerType == credentials.SignatureAnonymous {
|
2024-07-08 17:42:00 +00:00
|
|
|
// Fail if no credentials were found to prevent repeated attempts to (unsuccessfully) retrieve new credentials.
|
|
|
|
// The first attempt still has to timeout which slows down restic usage considerably. Thus, migrate towards forcing
|
|
|
|
// users to explicitly decide between authenticated and anonymous access.
|
|
|
|
if feature.Flag.Enabled(feature.ExplicitS3AnonymousAuth) {
|
|
|
|
return nil, fmt.Errorf("no credentials found. Use `-o s3.unsafe-anonymous-auth=true` for anonymous authentication")
|
|
|
|
}
|
|
|
|
|
2023-09-18 18:09:32 +00:00
|
|
|
debug.Log("using anonymous access for %#v", cfg.Endpoint)
|
2024-07-07 09:31:04 +00:00
|
|
|
creds = credentials.New(&credentials.Static{})
|
2023-09-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
roleArn := os.Getenv("RESTIC_AWS_ASSUME_ROLE_ARN")
|
|
|
|
if roleArn != "" {
|
|
|
|
// use the region provided by the configuration by default
|
|
|
|
awsRegion := cfg.Region
|
|
|
|
// allow the region to be overridden if for some reason it is required
|
2024-01-06 20:44:53 +00:00
|
|
|
if os.Getenv("RESTIC_AWS_ASSUME_ROLE_REGION") != "" {
|
2023-09-18 18:09:32 +00:00
|
|
|
awsRegion = os.Getenv("RESTIC_AWS_ASSUME_ROLE_REGION")
|
|
|
|
}
|
|
|
|
|
|
|
|
sessionName := os.Getenv("RESTIC_AWS_ASSUME_ROLE_SESSION_NAME")
|
|
|
|
externalID := os.Getenv("RESTIC_AWS_ASSUME_ROLE_EXTERNAL_ID")
|
|
|
|
policy := os.Getenv("RESTIC_AWS_ASSUME_ROLE_POLICY")
|
|
|
|
stsEndpoint := os.Getenv("RESTIC_AWS_ASSUME_ROLE_STS_ENDPOINT")
|
|
|
|
|
|
|
|
if stsEndpoint == "" {
|
2024-01-06 20:44:53 +00:00
|
|
|
if awsRegion != "" {
|
2023-09-18 18:09:32 +00:00
|
|
|
if strings.HasPrefix(awsRegion, "cn-") {
|
|
|
|
stsEndpoint = "https://sts." + awsRegion + ".amazonaws.com.cn"
|
|
|
|
} else {
|
|
|
|
stsEndpoint = "https://sts." + awsRegion + ".amazonaws.com"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
stsEndpoint = "https://sts.amazonaws.com"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := credentials.STSAssumeRoleOptions{
|
|
|
|
RoleARN: roleArn,
|
|
|
|
AccessKey: c.AccessKeyID,
|
|
|
|
SecretKey: c.SecretAccessKey,
|
|
|
|
SessionToken: c.SessionToken,
|
|
|
|
RoleSessionName: sessionName,
|
|
|
|
ExternalID: externalID,
|
|
|
|
Policy: policy,
|
2024-01-06 20:44:53 +00:00
|
|
|
Location: awsRegion,
|
2023-09-18 18:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
creds, err = credentials.NewSTSAssumeRole(stsEndpoint, opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "creds.AssumeRole")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return creds, nil
|
|
|
|
}
|
|
|
|
|
2017-06-17 20:15:58 +00:00
|
|
|
// Open opens the S3 backend at bucket and region. The bucket is created if it
|
|
|
|
// does not exist yet.
|
2024-08-26 19:16:22 +00:00
|
|
|
func Open(_ context.Context, cfg Config, rt http.RoundTripper) (backend.Backend, error) {
|
|
|
|
return open(cfg, rt)
|
2017-06-17 20:15:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create opens the S3 backend at bucket and region and creates the bucket if
|
|
|
|
// it does not exist yet.
|
2023-10-01 09:40:12 +00:00
|
|
|
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (backend.Backend, error) {
|
2024-08-26 19:16:22 +00:00
|
|
|
be, err := open(cfg, rt)
|
2017-07-17 08:33:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "open")
|
|
|
|
}
|
2020-09-19 19:57:02 +00:00
|
|
|
found, err := be.client.BucketExists(ctx, cfg.Bucket)
|
2018-03-02 08:47:20 +00:00
|
|
|
|
2022-06-13 18:35:37 +00:00
|
|
|
if err != nil && isAccessDenied(err) {
|
2018-03-02 08:47:20 +00:00
|
|
|
err = nil
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
|
2016-08-21 14:14:58 +00:00
|
|
|
if err != nil {
|
2016-12-05 22:12:30 +00:00
|
|
|
debug.Log("BucketExists(%v) returned err %v", cfg.Bucket, err)
|
2016-08-29 19:54:50 +00:00
|
|
|
return nil, errors.Wrap(err, "client.BucketExists")
|
2016-08-21 14:14:58 +00:00
|
|
|
}
|
2015-12-28 17:55:15 +00:00
|
|
|
|
2016-12-05 22:12:30 +00:00
|
|
|
if !found {
|
2016-01-03 20:46:07 +00:00
|
|
|
// create new bucket with default ACL in default region
|
2020-09-19 19:57:02 +00:00
|
|
|
err = be.client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{})
|
2016-01-03 20:46:07 +00:00
|
|
|
if err != nil {
|
2016-08-29 19:54:50 +00:00
|
|
|
return nil, errors.Wrap(err, "client.MakeBucket")
|
2016-01-03 20:46:07 +00:00
|
|
|
}
|
2015-11-06 21:31:59 +00:00
|
|
|
}
|
|
|
|
|
2015-12-06 22:21:48 +00:00
|
|
|
return be, nil
|
|
|
|
}
|
|
|
|
|
2022-06-13 18:35:37 +00:00
|
|
|
// isAccessDenied returns true if the error is caused by Access Denied.
|
|
|
|
func isAccessDenied(err error) bool {
|
|
|
|
debug.Log("isAccessDenied(%T, %#v)", err, err)
|
2018-03-02 08:47:20 +00:00
|
|
|
|
2022-06-13 18:35:37 +00:00
|
|
|
var e minio.ErrorResponse
|
2022-12-13 06:51:44 +00:00
|
|
|
return errors.As(err, &e) && e.Code == "AccessDenied"
|
2018-03-02 08:47:20 +00:00
|
|
|
}
|
|
|
|
|
2017-05-15 21:37:02 +00:00
|
|
|
// IsNotExist returns true if the error is caused by a not existing file.
|
2017-06-07 19:59:01 +00:00
|
|
|
func (be *Backend) IsNotExist(err error) bool {
|
2022-06-13 18:35:37 +00:00
|
|
|
var e minio.ErrorResponse
|
|
|
|
return errors.As(err, &e) && e.Code == "NoSuchKey"
|
2017-05-15 21:37:02 +00:00
|
|
|
}
|
|
|
|
|
2024-05-10 22:11:23 +00:00
|
|
|
func (be *Backend) IsPermanentError(err error) bool {
|
|
|
|
if be.IsNotExist(err) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
var merr minio.ErrorResponse
|
|
|
|
if errors.As(err, &merr) {
|
|
|
|
if merr.Code == "InvalidRange" || merr.Code == "AccessDenied" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-08-07 20:20:49 +00:00
|
|
|
func (be *Backend) Connections() uint {
|
|
|
|
return be.cfg.Connections
|
|
|
|
}
|
|
|
|
|
2020-12-19 11:39:48 +00:00
|
|
|
// Hasher may return a hash function for calculating a content hash for the backend
|
|
|
|
func (be *Backend) Hasher() hash.Hash {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-01 18:07:29 +00:00
|
|
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
|
|
|
func (be *Backend) HasAtomicReplace() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-06-07 20:54:37 +00:00
|
|
|
// Path returns the path in the bucket that is used for this backend.
|
|
|
|
func (be *Backend) Path() string {
|
2017-06-15 14:41:09 +00:00
|
|
|
return be.cfg.Prefix
|
2015-05-10 15:20:58 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 22:20:27 +00:00
|
|
|
// useStorageClass returns whether file should be saved in the provided Storage Class
|
2024-01-20 10:18:09 +00:00
|
|
|
// For archive storage classes, only data files are stored using that class; metadata
|
|
|
|
// must remain instantly accessible.
|
2023-12-04 22:20:27 +00:00
|
|
|
func (be *Backend) useStorageClass(h backend.Handle) bool {
|
2024-01-20 10:18:09 +00:00
|
|
|
notArchiveClass := be.cfg.StorageClass != "GLACIER" && be.cfg.StorageClass != "DEEP_ARCHIVE"
|
2023-12-04 22:20:27 +00:00
|
|
|
isDataFile := h.Type == backend.PackFile && !h.IsMetadata
|
|
|
|
return isDataFile || notArchiveClass
|
|
|
|
}
|
|
|
|
|
2016-01-24 00:15:35 +00:00
|
|
|
// Save stores data in the backend at the handle.
|
2023-10-01 09:40:12 +00:00
|
|
|
func (be *Backend) Save(ctx context.Context, h backend.Handle, rd backend.RewindReader) error {
|
2017-04-11 20:04:18 +00:00
|
|
|
objName := be.Filename(h)
|
2017-04-17 17:18:47 +00:00
|
|
|
|
2024-01-20 10:18:09 +00:00
|
|
|
opts := minio.PutObjectOptions{
|
|
|
|
ContentType: "application/octet-stream",
|
|
|
|
// the only option with the high-level api is to let the library handle the checksum computation
|
|
|
|
SendContentMd5: true,
|
|
|
|
// only use multipart uploads for very large files
|
|
|
|
PartSize: 200 * 1024 * 1024,
|
|
|
|
}
|
2023-12-04 22:20:27 +00:00
|
|
|
if be.useStorageClass(h) {
|
|
|
|
opts.StorageClass = be.cfg.StorageClass
|
|
|
|
}
|
2017-12-08 20:52:50 +00:00
|
|
|
|
2022-12-02 18:36:43 +00:00
|
|
|
info, err := be.client.PutObject(ctx, be.cfg.Bucket, objName, io.NopCloser(rd), int64(rd.Length()), opts)
|
2017-06-11 09:15:15 +00:00
|
|
|
|
2020-12-18 22:41:29 +00:00
|
|
|
// sanity check
|
2021-03-08 19:23:57 +00:00
|
|
|
if err == nil && info.Size != rd.Length() {
|
2020-12-18 22:41:29 +00:00
|
|
|
return errors.Errorf("wrote %d bytes instead of the expected %d bytes", info.Size, rd.Length())
|
|
|
|
}
|
2016-01-24 00:15:35 +00:00
|
|
|
|
2016-08-29 19:54:50 +00:00
|
|
|
return errors.Wrap(err, "client.PutObject")
|
2016-01-24 00:15:35 +00:00
|
|
|
}
|
|
|
|
|
2018-01-17 04:59:16 +00:00
|
|
|
// Load runs fn with a reader that yields the contents of the file at h at the
|
|
|
|
// given offset.
|
2023-10-01 09:40:12 +00:00
|
|
|
func (be *Backend) Load(ctx context.Context, h backend.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
2023-04-07 21:02:35 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
2023-10-01 08:24:33 +00:00
|
|
|
return util.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
|
2018-01-17 04:59:16 +00:00
|
|
|
}
|
|
|
|
|
2023-10-01 09:40:12 +00:00
|
|
|
func (be *Backend) openReader(ctx context.Context, h backend.Handle, length int, offset int64) (io.ReadCloser, error) {
|
2017-04-11 20:04:18 +00:00
|
|
|
objName := be.Filename(h)
|
2017-12-08 20:52:50 +00:00
|
|
|
opts := minio.GetObjectOptions{}
|
2017-01-22 21:01:12 +00:00
|
|
|
|
2017-12-08 20:52:50 +00:00
|
|
|
var err error
|
2017-05-13 19:18:14 +00:00
|
|
|
if length > 0 {
|
2017-12-08 20:52:50 +00:00
|
|
|
err = opts.SetRange(offset, offset+int64(length)-1)
|
|
|
|
} else if offset > 0 {
|
|
|
|
err = opts.SetRange(offset, 0)
|
2017-01-22 21:01:12 +00:00
|
|
|
}
|
2017-06-11 12:30:56 +00:00
|
|
|
|
2017-12-08 20:52:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "SetRange")
|
|
|
|
}
|
2017-01-22 21:01:12 +00:00
|
|
|
|
2017-06-05 22:17:39 +00:00
|
|
|
coreClient := minio.Core{Client: be.client}
|
2024-05-10 22:11:23 +00:00
|
|
|
rd, info, _, err := coreClient.GetObject(ctx, be.cfg.Bucket, objName, opts)
|
2017-05-13 22:09:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-01-22 21:01:12 +00:00
|
|
|
|
2024-05-12 09:55:34 +00:00
|
|
|
if feature.Flag.Enabled(feature.BackendErrorRedesign) && length > 0 {
|
2024-05-10 22:11:23 +00:00
|
|
|
if info.Size > 0 && info.Size != int64(length) {
|
|
|
|
_ = rd.Close()
|
|
|
|
return nil, minio.ErrorResponse{Code: "InvalidRange", Message: "restic-file-too-short"}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-07 21:02:35 +00:00
|
|
|
return rd, err
|
2017-01-22 21:01:12 +00:00
|
|
|
}
|
|
|
|
|
2016-01-23 22:27:58 +00:00
|
|
|
// Stat returns information about a blob.
|
2023-10-01 09:40:12 +00:00
|
|
|
func (be *Backend) Stat(ctx context.Context, h backend.Handle) (bi backend.FileInfo, err error) {
|
2017-04-11 20:04:18 +00:00
|
|
|
objName := be.Filename(h)
|
2016-08-21 14:15:24 +00:00
|
|
|
var obj *minio.Object
|
|
|
|
|
2017-12-08 20:52:50 +00:00
|
|
|
opts := minio.GetObjectOptions{}
|
|
|
|
|
2020-09-19 19:57:02 +00:00
|
|
|
obj, err = be.client.GetObject(ctx, be.cfg.Bucket, objName, opts)
|
2016-01-23 22:27:58 +00:00
|
|
|
if err != nil {
|
2023-10-01 09:40:12 +00:00
|
|
|
return backend.FileInfo{}, errors.Wrap(err, "client.GetObject")
|
2016-01-23 22:27:58 +00:00
|
|
|
}
|
|
|
|
|
2016-08-21 14:15:24 +00:00
|
|
|
// make sure that the object is closed properly.
|
|
|
|
defer func() {
|
|
|
|
e := obj.Close()
|
|
|
|
if err == nil {
|
2016-08-29 19:54:50 +00:00
|
|
|
err = errors.Wrap(e, "Close")
|
2016-08-21 14:15:24 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2016-01-23 22:27:58 +00:00
|
|
|
fi, err := obj.Stat()
|
|
|
|
if err != nil {
|
2023-10-01 09:40:12 +00:00
|
|
|
return backend.FileInfo{}, errors.Wrap(err, "Stat")
|
2016-01-23 22:27:58 +00:00
|
|
|
}
|
|
|
|
|
2023-10-01 09:40:12 +00:00
|
|
|
return backend.FileInfo{Size: fi.Size, Name: h.Name}, nil
|
2016-01-23 22:27:58 +00:00
|
|
|
}
|
|
|
|
|
2015-05-10 15:20:58 +00:00
|
|
|
// Remove removes the blob with the given name and type.
|
2023-10-01 09:40:12 +00:00
|
|
|
func (be *Backend) Remove(ctx context.Context, h backend.Handle) error {
|
2017-04-11 20:04:18 +00:00
|
|
|
objName := be.Filename(h)
|
2017-11-01 13:40:54 +00:00
|
|
|
|
2020-09-19 19:57:02 +00:00
|
|
|
err := be.client.RemoveObject(ctx, be.cfg.Bucket, objName, minio.RemoveObjectOptions{})
|
2017-11-01 13:40:54 +00:00
|
|
|
|
2017-06-15 14:06:47 +00:00
|
|
|
if be.IsNotExist(err) {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
|
2016-08-29 19:54:50 +00:00
|
|
|
return errors.Wrap(err, "client.RemoveObject")
|
2015-05-10 15:20:58 +00:00
|
|
|
}
|
|
|
|
|
2018-01-20 18:34:38 +00:00
|
|
|
// List runs fn for each file in the backend which has the type t. When an
|
|
|
|
// error occurs (or fn returns an error), List stops and returns it.
|
2023-10-01 09:40:12 +00:00
|
|
|
func (be *Backend) List(ctx context.Context, t backend.FileType, fn func(backend.FileInfo) error) error {
|
2017-12-14 18:13:01 +00:00
|
|
|
prefix, recursive := be.Basedir(t)
|
2015-05-10 15:20:58 +00:00
|
|
|
|
2017-05-15 21:37:02 +00:00
|
|
|
// make sure prefix ends with a slash
|
2018-01-20 18:34:38 +00:00
|
|
|
if !strings.HasSuffix(prefix, "/") {
|
2017-05-15 21:37:02 +00:00
|
|
|
prefix += "/"
|
|
|
|
}
|
|
|
|
|
2018-01-20 18:34:38 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
2020-11-11 10:49:20 +00:00
|
|
|
debug.Log("using ListObjectsV1(%v)", be.cfg.ListObjectsV1)
|
|
|
|
|
2017-11-02 22:29:32 +00:00
|
|
|
// NB: unfortunately we can't protect this with be.sem.GetToken() here.
|
|
|
|
// Doing so would enable a deadlock situation (gh-1399), as ListObjects()
|
|
|
|
// starts its own goroutine and returns results via a channel.
|
2020-09-19 19:57:02 +00:00
|
|
|
listresp := be.client.ListObjects(ctx, be.cfg.Bucket, minio.ListObjectsOptions{
|
|
|
|
Prefix: prefix,
|
|
|
|
Recursive: recursive,
|
2020-11-11 10:49:20 +00:00
|
|
|
UseV1: be.cfg.ListObjectsV1,
|
2020-09-19 19:57:02 +00:00
|
|
|
})
|
2015-05-13 17:48:52 +00:00
|
|
|
|
2018-01-20 18:34:38 +00:00
|
|
|
for obj := range listresp {
|
2018-06-01 20:15:23 +00:00
|
|
|
if obj.Err != nil {
|
|
|
|
return obj.Err
|
|
|
|
}
|
|
|
|
|
2018-01-20 18:34:38 +00:00
|
|
|
m := strings.TrimPrefix(obj.Key, prefix)
|
|
|
|
if m == "" {
|
|
|
|
continue
|
2015-05-10 15:20:58 +00:00
|
|
|
}
|
|
|
|
|
2023-10-01 09:40:12 +00:00
|
|
|
fi := backend.FileInfo{
|
2018-01-20 18:34:38 +00:00
|
|
|
Name: path.Base(m),
|
|
|
|
Size: obj.Size,
|
|
|
|
}
|
2015-05-10 15:20:58 +00:00
|
|
|
|
2018-01-20 18:34:38 +00:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
err := fn(fi)
|
2015-12-06 22:21:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-01-20 18:34:38 +00:00
|
|
|
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
2015-06-14 13:02:29 +00:00
|
|
|
}
|
2015-12-06 22:21:48 +00:00
|
|
|
|
2018-01-20 18:34:38 +00:00
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
|
2015-12-19 12:23:05 +00:00
|
|
|
// Delete removes all restic keys in the bucket. It will not remove the bucket itself.
|
2017-06-07 19:59:01 +00:00
|
|
|
func (be *Backend) Delete(ctx context.Context) error {
|
2023-10-01 08:24:33 +00:00
|
|
|
return util.DefaultDelete(ctx, be)
|
2015-05-10 15:20:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close does nothing
|
2017-06-07 19:59:01 +00:00
|
|
|
func (be *Backend) Close() error { return nil }
|