| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | package s3 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2017-06-03 17:39:57 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	"io/ioutil" | 
					
						
							| 
									
										
										
										
											2017-09-24 20:04:23 +02:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2017-05-13 23:55:22 +02:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2016-02-15 09:47:10 -08:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-23 14:21:03 +02:00
										 |  |  | 	"github.com/restic/restic/internal/backend" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/errors" | 
					
						
							| 
									
										
										
										
											2017-07-24 17:42:25 +02:00
										 |  |  | 	"github.com/restic/restic/internal/restic" | 
					
						
							| 
									
										
										
										
											2016-08-21 17:46:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-06 15:31:59 -06:00
										 |  |  | 	"github.com/minio/minio-go" | 
					
						
							| 
									
										
										
										
											2017-07-05 16:19:25 +02:00
										 |  |  | 	"github.com/minio/minio-go/pkg/credentials" | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-23 14:21:03 +02:00
										 |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | // Backend stores data on an S3 endpoint. | 
					
						
							|  |  |  | type Backend struct { | 
					
						
							| 
									
										
										
										
											2017-06-15 16:41:09 +02:00
										 |  |  | 	client *minio.Client | 
					
						
							|  |  |  | 	sem    *backend.Semaphore | 
					
						
							|  |  |  | 	cfg    Config | 
					
						
							| 
									
										
										
										
											2017-04-11 22:04:18 +02:00
										 |  |  | 	backend.Layout | 
					
						
							| 
									
										
										
										
											2015-05-15 17:29:48 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | // make sure that *Backend implements backend.Backend | 
					
						
							|  |  |  | var _ restic.Backend = &Backend{} | 
					
						
							| 
									
										
										
										
											2017-06-03 17:39:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-07 23:08:20 +02:00
										 |  |  | const defaultLayout = "default" | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 20:04:23 +02:00
										 |  |  | func open(cfg Config, rt http.RoundTripper) (*Backend, error) { | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 	debug.Log("open, config %#v", cfg) | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-12 21:09:26 +02:00
										 |  |  | 	if cfg.MaxRetries > 0 { | 
					
						
							|  |  |  | 		minio.MaxRetry = int(cfg.MaxRetries) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-09 12:48:42 -07:00
										 |  |  | 	// Chains all credential types, starting with | 
					
						
							|  |  |  | 	// Static credentials provided by user. | 
					
						
							|  |  |  | 	// IAM profile based credentials. (performs an HTTP | 
					
						
							|  |  |  | 	// call to a pre-defined endpoint, only valid inside | 
					
						
							|  |  |  | 	// configured ec2 instances) | 
					
						
							|  |  |  | 	// AWS env variables such as AWS_ACCESS_KEY_ID | 
					
						
							|  |  |  | 	// Minio env variables such as MINIO_ACCESS_KEY | 
					
						
							| 
									
										
										
										
											2017-12-09 01:56:49 -08:00
										 |  |  | 	creds := credentials.NewChainCredentials([]credentials.Provider{ | 
					
						
							| 
									
										
										
										
											2017-10-09 12:48:42 -07:00
										 |  |  | 		&credentials.Static{ | 
					
						
							|  |  |  | 			Value: credentials.Value{ | 
					
						
							|  |  |  | 				AccessKeyID:     cfg.KeyID, | 
					
						
							|  |  |  | 				SecretAccessKey: cfg.Secret, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		&credentials.IAM{ | 
					
						
							|  |  |  | 			Client: &http.Client{ | 
					
						
							|  |  |  | 				Transport: http.DefaultTransport, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		&credentials.EnvAWS{}, | 
					
						
							|  |  |  | 		&credentials.EnvMinio{}, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	client, err := minio.NewWithCredentials(cfg.Endpoint, creds, !cfg.UseHTTP, "") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, errors.Wrap(err, "minio.NewWithCredentials") | 
					
						
							| 
									
										
										
										
											2015-08-26 06:25:05 -05:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-06 00:17:39 +02:00
										 |  |  | 	sem, err := backend.NewSemaphore(cfg.Connections) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | 	be := &Backend{ | 
					
						
							| 
									
										
										
										
											2017-06-15 16:41:09 +02:00
										 |  |  | 		client: client, | 
					
						
							|  |  |  | 		sem:    sem, | 
					
						
							|  |  |  | 		cfg:    cfg, | 
					
						
							| 
									
										
										
										
											2017-03-14 23:05:51 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-02-10 19:24:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-24 20:04:23 +02:00
										 |  |  | 	client.SetCustomTransport(rt) | 
					
						
							| 
									
										
										
										
											2017-02-10 19:24:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 	l, err := backend.ParseLayout(be, cfg.Layout, defaultLayout, cfg.Prefix) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	be.Layout = l | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-17 22:15:58 +02:00
										 |  |  | 	return be, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Open opens the S3 backend at bucket and region. The bucket is created if it | 
					
						
							|  |  |  | // does not exist yet. | 
					
						
							| 
									
										
										
										
											2017-09-24 20:04:23 +02:00
										 |  |  | func Open(cfg Config, rt http.RoundTripper) (restic.Backend, error) { | 
					
						
							|  |  |  | 	return open(cfg, rt) | 
					
						
							| 
									
										
										
										
											2017-06-17 22:15:58 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Create opens the S3 backend at bucket and region and creates the bucket if | 
					
						
							|  |  |  | // it does not exist yet. | 
					
						
							| 
									
										
										
										
											2017-09-24 20:04:23 +02:00
										 |  |  | func Create(cfg Config, rt http.RoundTripper) (restic.Backend, error) { | 
					
						
							|  |  |  | 	be, err := open(cfg, rt) | 
					
						
							| 
									
										
										
										
											2017-07-17 11:33:19 +03:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, errors.Wrap(err, "open") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-06-17 22:15:58 +02:00
										 |  |  | 	found, err := be.client.BucketExists(cfg.Bucket) | 
					
						
							| 
									
										
										
										
											2018-03-02 10:47:20 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if err != nil && be.IsAccessDenied(err) { | 
					
						
							|  |  |  | 		err = nil | 
					
						
							|  |  |  | 		found = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-21 16:14:58 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-12-05 23:12:30 +01:00
										 |  |  | 		debug.Log("BucketExists(%v) returned err %v", cfg.Bucket, err) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 		return nil, errors.Wrap(err, "client.BucketExists") | 
					
						
							| 
									
										
										
										
											2016-08-21 16:14:58 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-12-28 18:55:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-05 23:12:30 +01:00
										 |  |  | 	if !found { | 
					
						
							| 
									
										
										
										
											2016-01-03 21:46:07 +01:00
										 |  |  | 		// create new bucket with default ACL in default region | 
					
						
							| 
									
										
										
										
											2017-06-17 22:15:58 +02:00
										 |  |  | 		err = be.client.MakeBucket(cfg.Bucket, "") | 
					
						
							| 
									
										
										
										
											2016-01-03 21:46:07 +01:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 			return nil, errors.Wrap(err, "client.MakeBucket") | 
					
						
							| 
									
										
										
										
											2016-01-03 21:46:07 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-11-06 15:31:59 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-06 23:21:48 +01:00
										 |  |  | 	return be, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-02 10:47:20 +02:00
										 |  |  | // IsAccessDenied returns true if the error is caused by Access Denied. | 
					
						
							|  |  |  | func (be *Backend) IsAccessDenied(err error) bool { | 
					
						
							|  |  |  | 	debug.Log("IsAccessDenied(%T, %#v)", err, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if e, ok := errors.Cause(err).(minio.ErrorResponse); ok && e.Code == "AccessDenied" { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | // IsNotExist returns true if the error is caused by a not existing file. | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) IsNotExist(err error) bool { | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 	debug.Log("IsNotExist(%T, %#v)", err, err) | 
					
						
							| 
									
										
										
										
											2017-06-16 10:54:46 +02:00
										 |  |  | 	if os.IsNotExist(errors.Cause(err)) { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if e, ok := errors.Cause(err).(minio.ErrorResponse); ok && e.Code == "NoSuchKey" { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Join combines path components with slashes. | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) Join(p ...string) string { | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 	return path.Join(p...) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type fileInfo struct { | 
					
						
							|  |  |  | 	name    string | 
					
						
							|  |  |  | 	size    int64 | 
					
						
							|  |  |  | 	mode    os.FileMode | 
					
						
							|  |  |  | 	modTime time.Time | 
					
						
							|  |  |  | 	isDir   bool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (fi fileInfo) Name() string       { return fi.name }    // base name of the file | 
					
						
							|  |  |  | func (fi fileInfo) Size() int64        { return fi.size }    // length in bytes for regular files; system-dependent for others | 
					
						
							|  |  |  | func (fi fileInfo) Mode() os.FileMode  { return fi.mode }    // file mode bits | 
					
						
							|  |  |  | func (fi fileInfo) ModTime() time.Time { return fi.modTime } // modification time | 
					
						
							|  |  |  | func (fi fileInfo) IsDir() bool        { return fi.isDir }   // abbreviation for Mode().IsDir() | 
					
						
							|  |  |  | func (fi fileInfo) Sys() interface{}   { return nil }        // underlying data source (can return nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ReadDir returns the entries for a directory. | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) ReadDir(dir string) (list []os.FileInfo, err error) { | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 	debug.Log("ReadDir(%v)", dir) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// make sure dir ends with a slash | 
					
						
							|  |  |  | 	if dir[len(dir)-1] != '/' { | 
					
						
							|  |  |  | 		dir += "/" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	done := make(chan struct{}) | 
					
						
							|  |  |  | 	defer close(done) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-15 16:41:09 +02:00
										 |  |  | 	for obj := range be.client.ListObjects(be.cfg.Bucket, dir, false, done) { | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 		if obj.Key == "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		name := strings.TrimPrefix(obj.Key, dir) | 
					
						
							| 
									
										
										
										
											2017-10-18 13:45:31 -07:00
										 |  |  | 		// Sometimes s3 returns an entry for the dir itself. Ignore it. | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 		if name == "" { | 
					
						
							| 
									
										
										
										
											2017-10-18 13:45:31 -07:00
										 |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		entry := fileInfo{ | 
					
						
							|  |  |  | 			name:    name, | 
					
						
							|  |  |  | 			size:    obj.Size, | 
					
						
							|  |  |  | 			modTime: obj.LastModified, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if name[len(name)-1] == '/' { | 
					
						
							|  |  |  | 			entry.isDir = true | 
					
						
							|  |  |  | 			entry.mode = os.ModeDir | 0755 | 
					
						
							|  |  |  | 			entry.name = name[:len(name)-1] | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			entry.mode = 0644 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		list = append(list, entry) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return list, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-13 12:48:52 -05:00
										 |  |  | // Location returns this backend's location (the bucket name). | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) Location() string { | 
					
						
							| 
									
										
										
										
											2017-06-15 16:41:09 +02:00
										 |  |  | 	return be.Join(be.cfg.Bucket, be.cfg.Prefix) | 
					
						
							| 
									
										
										
										
											2017-06-07 22:54:37 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Path returns the path in the bucket that is used for this backend. | 
					
						
							|  |  |  | func (be *Backend) Path() string { | 
					
						
							| 
									
										
										
										
											2017-06-15 16:41:09 +02:00
										 |  |  | 	return be.cfg.Prefix | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | // lenForFile returns the length of the file. | 
					
						
							|  |  |  | func lenForFile(f *os.File) (int64, error) { | 
					
						
							| 
									
										
										
										
											2017-09-22 11:33:16 -07:00
										 |  |  | 	fi, err := f.Stat() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 		return 0, errors.Wrap(err, "Stat") | 
					
						
							| 
									
										
										
										
											2017-09-22 11:33:16 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pos, err := f.Seek(0, io.SeekCurrent) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 		return 0, errors.Wrap(err, "Seek") | 
					
						
							| 
									
										
										
										
											2017-09-22 11:33:16 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	size := fi.Size() - pos | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	return size, nil | 
					
						
							| 
									
										
										
										
											2017-09-22 11:33:16 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-24 01:15:35 +01:00
										 |  |  | // Save stores data in the backend at the handle. | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err error) { | 
					
						
							| 
									
										
										
										
											2017-06-11 11:15:15 +02:00
										 |  |  | 	debug.Log("Save %v", h) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-24 01:15:35 +01:00
										 |  |  | 	if err := h.Valid(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-11 22:04:18 +02:00
										 |  |  | 	objName := be.Filename(h) | 
					
						
							| 
									
										
										
										
											2017-04-17 19:18:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-01 09:40:54 -04:00
										 |  |  | 	be.sem.GetToken() | 
					
						
							|  |  |  | 	defer be.sem.ReleaseToken() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	var size int64 = -1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	type lenner interface { | 
					
						
							|  |  |  | 		Len() int | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// find size for reader | 
					
						
							| 
									
										
										
										
											2017-09-22 11:33:16 -07:00
										 |  |  | 	if f, ok := rd.(*os.File); ok { | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 		size, err = lenForFile(f) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-09-22 11:33:16 -07:00
										 |  |  | 	} else if l, ok := rd.(lenner); ok { | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 		size = int64(l.Len()) | 
					
						
							| 
									
										
										
										
											2017-09-22 11:33:16 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-06-15 16:39:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	opts := minio.PutObjectOptions{} | 
					
						
							|  |  |  | 	opts.ContentType = "application/octet-stream" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	debug.Log("PutObject(%v, %v, %v)", be.cfg.Bucket, objName, size) | 
					
						
							| 
									
										
										
										
											2017-12-08 22:04:55 +01:00
										 |  |  | 	n, err := be.client.PutObjectWithContext(ctx, be.cfg.Bucket, objName, ioutil.NopCloser(rd), size, opts) | 
					
						
							| 
									
										
										
										
											2017-06-11 11:15:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	debug.Log("%v -> %v bytes, err %#v: %v", objName, n, err, err) | 
					
						
							| 
									
										
										
										
											2016-01-24 01:15:35 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 	return errors.Wrap(err, "client.PutObject") | 
					
						
							| 
									
										
										
										
											2016-01-24 01:15:35 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-10 19:25:50 +01:00
										 |  |  | // wrapReader wraps an io.ReadCloser to run an additional function on Close. | 
					
						
							|  |  |  | type wrapReader struct { | 
					
						
							|  |  |  | 	io.ReadCloser | 
					
						
							|  |  |  | 	f func() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (wr wrapReader) Close() error { | 
					
						
							|  |  |  | 	err := wr.ReadCloser.Close() | 
					
						
							|  |  |  | 	wr.f() | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-16 23:59:16 -05:00
										 |  |  | // Load runs fn with a reader that yields the contents of the file at h at the | 
					
						
							|  |  |  | // given offset. | 
					
						
							|  |  |  | func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error { | 
					
						
							|  |  |  | 	return backend.DefaultLoad(ctx, h, length, offset, be.openReader, fn) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (be *Backend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) { | 
					
						
							| 
									
										
										
										
											2017-04-17 19:18:47 +02:00
										 |  |  | 	debug.Log("Load %v, length %v, offset %v from %v", h, length, offset, be.Filename(h)) | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 	if err := h.Valid(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if offset < 0 { | 
					
						
							|  |  |  | 		return nil, errors.New("offset is negative") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if length < 0 { | 
					
						
							|  |  |  | 		return nil, errors.Errorf("invalid length %d", length) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-11 22:04:18 +02:00
										 |  |  | 	objName := be.Filename(h) | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	opts := minio.GetObjectOptions{} | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	var err error | 
					
						
							| 
									
										
										
										
											2017-05-13 21:18:14 +02:00
										 |  |  | 	if length > 0 { | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 		debug.Log("range: %v-%v", offset, offset+int64(length)-1) | 
					
						
							|  |  |  | 		err = opts.SetRange(offset, offset+int64(length)-1) | 
					
						
							|  |  |  | 	} else if offset > 0 { | 
					
						
							|  |  |  | 		debug.Log("range: %v-", offset) | 
					
						
							|  |  |  | 		err = opts.SetRange(offset, 0) | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-06-11 14:30:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, errors.Wrap(err, "SetRange") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	be.sem.GetToken() | 
					
						
							| 
									
										
										
										
											2017-06-06 00:17:39 +02:00
										 |  |  | 	coreClient := minio.Core{Client: be.client} | 
					
						
							| 
									
										
										
										
											2017-12-08 22:04:55 +01:00
										 |  |  | 	rd, err := coreClient.GetObjectWithContext(ctx, be.cfg.Bucket, objName, opts) | 
					
						
							| 
									
										
										
										
											2017-05-14 00:09:59 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-06-06 00:17:39 +02:00
										 |  |  | 		be.sem.ReleaseToken() | 
					
						
							| 
									
										
										
										
											2017-05-14 00:09:59 +02:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-14 00:09:59 +02:00
										 |  |  | 	closeRd := wrapReader{ | 
					
						
							|  |  |  | 		ReadCloser: rd, | 
					
						
							|  |  |  | 		f: func() { | 
					
						
							|  |  |  | 			debug.Log("Close()") | 
					
						
							| 
									
										
										
										
											2017-06-06 00:17:39 +02:00
										 |  |  | 			be.sem.ReleaseToken() | 
					
						
							| 
									
										
										
										
											2017-05-14 00:09:59 +02:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-14 00:09:59 +02:00
										 |  |  | 	return closeRd, err | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | // Stat returns information about a blob. | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) Stat(ctx context.Context, h restic.Handle) (bi restic.FileInfo, err error) { | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 	debug.Log("%v", h) | 
					
						
							| 
									
										
										
										
											2016-08-21 16:15:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-11 22:04:18 +02:00
										 |  |  | 	objName := be.Filename(h) | 
					
						
							| 
									
										
										
										
											2016-08-21 16:15:24 +02:00
										 |  |  | 	var obj *minio.Object | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	opts := minio.GetObjectOptions{} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-01 09:40:54 -04:00
										 |  |  | 	be.sem.GetToken() | 
					
						
							| 
									
										
										
										
											2017-12-08 22:04:55 +01:00
										 |  |  | 	obj, err = be.client.GetObjectWithContext(ctx, be.cfg.Bucket, objName, opts) | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 		debug.Log("GetObject() err %v", err) | 
					
						
							| 
									
										
										
										
											2017-11-01 09:40:54 -04:00
										 |  |  | 		be.sem.ReleaseToken() | 
					
						
							| 
									
										
										
										
											2016-08-31 22:39:36 +02:00
										 |  |  | 		return restic.FileInfo{}, errors.Wrap(err, "client.GetObject") | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-21 16:15:24 +02:00
										 |  |  | 	// make sure that the object is closed properly. | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		e := obj.Close() | 
					
						
							| 
									
										
										
										
											2017-11-01 09:40:54 -04:00
										 |  |  | 		be.sem.ReleaseToken() | 
					
						
							| 
									
										
										
										
											2016-08-21 16:15:24 +02:00
										 |  |  | 		if err == nil { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 			err = errors.Wrap(e, "Close") | 
					
						
							| 
									
										
										
										
											2016-08-21 16:15:24 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | 	fi, err := obj.Stat() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 		debug.Log("Stat() err %v", err) | 
					
						
							| 
									
										
										
										
											2016-08-31 22:39:36 +02:00
										 |  |  | 		return restic.FileInfo{}, errors.Wrap(err, "Stat") | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 	return restic.FileInfo{Size: fi.Size, Name: h.Name}, nil | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | // Test returns true if a blob of the given type and name exists in the backend. | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) Test(ctx context.Context, h restic.Handle) (bool, error) { | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 	found := false | 
					
						
							| 
									
										
										
										
											2017-04-11 22:04:18 +02:00
										 |  |  | 	objName := be.Filename(h) | 
					
						
							| 
									
										
										
										
											2017-11-01 09:40:54 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	be.sem.GetToken() | 
					
						
							| 
									
										
										
										
											2017-12-08 21:52:50 +01:00
										 |  |  | 	_, err := be.client.StatObject(be.cfg.Bucket, objName, minio.StatObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2017-11-01 09:40:54 -04:00
										 |  |  | 	be.sem.ReleaseToken() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-26 06:25:05 -05:00
										 |  |  | 	if err == nil { | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 		found = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If error, then not found | 
					
						
							|  |  |  | 	return found, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Remove removes the blob with the given name and type. | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) Remove(ctx context.Context, h restic.Handle) error { | 
					
						
							| 
									
										
										
										
											2017-04-11 22:04:18 +02:00
										 |  |  | 	objName := be.Filename(h) | 
					
						
							| 
									
										
										
										
											2017-11-01 09:40:54 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	be.sem.GetToken() | 
					
						
							| 
									
										
										
										
											2017-06-15 16:41:09 +02:00
										 |  |  | 	err := be.client.RemoveObject(be.cfg.Bucket, objName) | 
					
						
							| 
									
										
										
										
											2017-11-01 09:40:54 -04:00
										 |  |  | 	be.sem.ReleaseToken() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-17 19:18:47 +02:00
										 |  |  | 	debug.Log("Remove(%v) at %v -> err %v", h, objName, err) | 
					
						
							| 
									
										
										
										
											2017-06-15 16:06:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if be.IsNotExist(err) { | 
					
						
							|  |  |  | 		err = nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 	return errors.Wrap(err, "client.RemoveObject") | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01: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. | 
					
						
							|  |  |  | func (be *Backend) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error { | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 	debug.Log("listing %v", t) | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-14 19:13:01 +01:00
										 |  |  | 	prefix, recursive := be.Basedir(t) | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 	// make sure prefix ends with a slash | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 	if !strings.HasSuffix(prefix, "/") { | 
					
						
							| 
									
										
										
										
											2017-05-15 23:37:02 +02:00
										 |  |  | 		prefix += "/" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 	ctx, cancel := context.WithCancel(ctx) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-02 18:29:32 -04: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. | 
					
						
							| 
									
										
										
										
											2017-12-14 19:13:01 +01:00
										 |  |  | 	listresp := be.client.ListObjects(be.cfg.Bucket, prefix, recursive, ctx.Done()) | 
					
						
							| 
									
										
										
										
											2015-05-13 12:48:52 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 	for obj := range listresp { | 
					
						
							|  |  |  | 		m := strings.TrimPrefix(obj.Key, prefix) | 
					
						
							|  |  |  | 		if m == "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 		fi := restic.FileInfo{ | 
					
						
							|  |  |  | 			Name: path.Base(m), | 
					
						
							|  |  |  | 			Size: obj.Size, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 		if ctx.Err() != nil { | 
					
						
							|  |  |  | 			return ctx.Err() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err := fn(fi) | 
					
						
							| 
									
										
										
										
											2015-12-06 23:21:48 +01:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if ctx.Err() != nil { | 
					
						
							|  |  |  | 			return ctx.Err() | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-06-14 08:02:29 -05:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-12-06 23:21:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 	return ctx.Err() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Remove keys for a specified backend type. | 
					
						
							|  |  |  | func (be *Backend) removeKeys(ctx context.Context, t restic.FileType) error { | 
					
						
							|  |  |  | 	return be.List(ctx, restic.DataFile, func(fi restic.FileInfo) error { | 
					
						
							|  |  |  | 		return be.Remove(ctx, restic.Handle{Type: t, Name: fi.Name}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2015-06-14 08:02:29 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-19 13:23:05 +01:00
										 |  |  | // Delete removes all restic keys in the bucket. It will not remove the bucket itself. | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) Delete(ctx context.Context) error { | 
					
						
							| 
									
										
										
										
											2016-08-31 22:39:36 +02:00
										 |  |  | 	alltypes := []restic.FileType{ | 
					
						
							|  |  |  | 		restic.DataFile, | 
					
						
							|  |  |  | 		restic.KeyFile, | 
					
						
							|  |  |  | 		restic.LockFile, | 
					
						
							|  |  |  | 		restic.SnapshotFile, | 
					
						
							|  |  |  | 		restic.IndexFile} | 
					
						
							| 
									
										
										
										
											2015-12-06 23:21:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for _, t := range alltypes { | 
					
						
							| 
									
										
										
										
											2017-06-03 17:39:57 +02:00
										 |  |  | 		err := be.removeKeys(ctx, t) | 
					
						
							| 
									
										
										
										
											2015-12-06 23:21:48 +01:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-03 17:39:57 +02:00
										 |  |  | 	return be.Remove(ctx, restic.Handle{Type: restic.ConfigFile}) | 
					
						
							| 
									
										
										
										
											2015-05-10 10:20:58 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Close does nothing | 
					
						
							| 
									
										
										
										
											2017-06-07 21:59:01 +02:00
										 |  |  | func (be *Backend) Close() error { return nil } | 
					
						
							| 
									
										
										
										
											2017-06-07 22:54:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Rename moves a file based on the new layout l. | 
					
						
							|  |  |  | func (be *Backend) Rename(h restic.Handle, l backend.Layout) error { | 
					
						
							|  |  |  | 	debug.Log("Rename %v to %v", h, l) | 
					
						
							|  |  |  | 	oldname := be.Filename(h) | 
					
						
							|  |  |  | 	newname := l.Filename(h) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-02 10:29:41 +02:00
										 |  |  | 	if oldname == newname { | 
					
						
							|  |  |  | 		debug.Log("  %v is already renamed", newname) | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-07 22:54:37 +02:00
										 |  |  | 	debug.Log("  %v -> %v", oldname, newname) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-17 20:43:45 +02:00
										 |  |  | 	src := minio.NewSourceInfo(be.cfg.Bucket, oldname, nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dst, err := minio.NewDestinationInfo(be.cfg.Bucket, newname, nil, nil) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return errors.Wrap(err, "NewDestinationInfo") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = be.client.CopyObject(dst, src) | 
					
						
							| 
									
										
										
										
											2017-07-02 10:47:50 +02:00
										 |  |  | 	if err != nil && be.IsNotExist(err) { | 
					
						
							|  |  |  | 		debug.Log("copy failed: %v, seems to already have been renamed", err) | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-07 22:54:37 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		debug.Log("copy failed: %v", err) | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-15 16:41:09 +02:00
										 |  |  | 	return be.client.RemoveObject(be.cfg.Bucket, oldname) | 
					
						
							| 
									
										
										
										
											2017-06-07 22:54:37 +02:00
										 |  |  | } |