github.com/mckael/restic@v0.8.3/internal/backend/swift/swift.go (about)

     1  package swift
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"path"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/restic/restic/internal/backend"
    13  	"github.com/restic/restic/internal/debug"
    14  	"github.com/restic/restic/internal/errors"
    15  	"github.com/restic/restic/internal/restic"
    16  
    17  	"github.com/ncw/swift"
    18  )
    19  
    20  const connLimit = 10
    21  
    22  // beSwift is a backend which stores the data on a swift endpoint.
    23  type beSwift struct {
    24  	conn      *swift.Connection
    25  	sem       *backend.Semaphore
    26  	container string // Container name
    27  	prefix    string // Prefix of object names in the container
    28  	backend.Layout
    29  }
    30  
    31  // ensure statically that *beSwift implements restic.Backend.
    32  var _ restic.Backend = &beSwift{}
    33  
    34  // Open opens the swift backend at a container in region. The container is
    35  // created if it does not exist yet.
    36  func Open(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
    37  	debug.Log("config %#v", cfg)
    38  
    39  	sem, err := backend.NewSemaphore(cfg.Connections)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	be := &beSwift{
    45  		conn: &swift.Connection{
    46  			UserName:       cfg.UserName,
    47  			Domain:         cfg.Domain,
    48  			ApiKey:         cfg.APIKey,
    49  			AuthUrl:        cfg.AuthURL,
    50  			Region:         cfg.Region,
    51  			Tenant:         cfg.Tenant,
    52  			TenantId:       cfg.TenantID,
    53  			TenantDomain:   cfg.TenantDomain,
    54  			TrustId:        cfg.TrustID,
    55  			StorageUrl:     cfg.StorageURL,
    56  			AuthToken:      cfg.AuthToken,
    57  			ConnectTimeout: time.Minute,
    58  			Timeout:        time.Minute,
    59  
    60  			Transport: rt,
    61  		},
    62  		sem:       sem,
    63  		container: cfg.Container,
    64  		prefix:    cfg.Prefix,
    65  		Layout: &backend.DefaultLayout{
    66  			Path: cfg.Prefix,
    67  			Join: path.Join,
    68  		},
    69  	}
    70  
    71  	// Authenticate if needed
    72  	if !be.conn.Authenticated() {
    73  		if err := be.conn.Authenticate(); err != nil {
    74  			return nil, errors.Wrap(err, "conn.Authenticate")
    75  		}
    76  	}
    77  
    78  	// Ensure container exists
    79  	switch _, _, err := be.conn.Container(be.container); err {
    80  	case nil:
    81  		// Container exists
    82  
    83  	case swift.ContainerNotFound:
    84  		err = be.createContainer(cfg.DefaultContainerPolicy)
    85  		if err != nil {
    86  			return nil, errors.Wrap(err, "beSwift.createContainer")
    87  		}
    88  
    89  	default:
    90  		return nil, errors.Wrap(err, "conn.Container")
    91  	}
    92  
    93  	return be, nil
    94  }
    95  
    96  func (be *beSwift) createContainer(policy string) error {
    97  	var h swift.Headers
    98  	if policy != "" {
    99  		h = swift.Headers{
   100  			"X-Storage-Policy": policy,
   101  		}
   102  	}
   103  
   104  	return be.conn.ContainerCreate(be.container, h)
   105  }
   106  
   107  // Location returns this backend's location (the container name).
   108  func (be *beSwift) Location() string {
   109  	return be.container
   110  }
   111  
   112  // Load runs fn with a reader that yields the contents of the file at h at the
   113  // given offset.
   114  func (be *beSwift) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
   115  	return backend.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
   116  }
   117  
   118  func (be *beSwift) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
   119  	debug.Log("Load %v, length %v, offset %v", h, length, offset)
   120  	if err := h.Valid(); err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	if offset < 0 {
   125  		return nil, errors.New("offset is negative")
   126  	}
   127  
   128  	if length < 0 {
   129  		return nil, errors.Errorf("invalid length %d", length)
   130  	}
   131  
   132  	objName := be.Filename(h)
   133  
   134  	headers := swift.Headers{}
   135  	if offset > 0 {
   136  		headers["Range"] = fmt.Sprintf("bytes=%d-", offset)
   137  	}
   138  
   139  	if length > 0 {
   140  		headers["Range"] = fmt.Sprintf("bytes=%d-%d", offset, offset+int64(length)-1)
   141  	}
   142  
   143  	if _, ok := headers["Range"]; ok {
   144  		debug.Log("Load(%v) send range %v", h, headers["Range"])
   145  	}
   146  
   147  	be.sem.GetToken()
   148  	obj, _, err := be.conn.ObjectOpen(be.container, objName, false, headers)
   149  	if err != nil {
   150  		debug.Log("  err %v", err)
   151  		be.sem.ReleaseToken()
   152  		return nil, errors.Wrap(err, "conn.ObjectOpen")
   153  	}
   154  
   155  	return be.sem.ReleaseTokenOnClose(obj, nil), nil
   156  }
   157  
   158  // Save stores data in the backend at the handle.
   159  func (be *beSwift) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err error) {
   160  	if err = h.Valid(); err != nil {
   161  		return err
   162  	}
   163  
   164  	objName := be.Filename(h)
   165  
   166  	debug.Log("Save %v at %v", h, objName)
   167  
   168  	be.sem.GetToken()
   169  	defer be.sem.ReleaseToken()
   170  
   171  	encoding := "binary/octet-stream"
   172  
   173  	debug.Log("PutObject(%v, %v, %v)", be.container, objName, encoding)
   174  	_, err = be.conn.ObjectPut(be.container, objName, rd, true, "", encoding, nil)
   175  	debug.Log("%v, err %#v", objName, err)
   176  
   177  	return errors.Wrap(err, "client.PutObject")
   178  }
   179  
   180  // Stat returns information about a blob.
   181  func (be *beSwift) Stat(ctx context.Context, h restic.Handle) (bi restic.FileInfo, err error) {
   182  	debug.Log("%v", h)
   183  
   184  	objName := be.Filename(h)
   185  
   186  	be.sem.GetToken()
   187  	defer be.sem.ReleaseToken()
   188  
   189  	obj, _, err := be.conn.Object(be.container, objName)
   190  	if err != nil {
   191  		debug.Log("Object() err %v", err)
   192  		return restic.FileInfo{}, errors.Wrap(err, "conn.Object")
   193  	}
   194  
   195  	return restic.FileInfo{Size: obj.Bytes, Name: h.Name}, nil
   196  }
   197  
   198  // Test returns true if a blob of the given type and name exists in the backend.
   199  func (be *beSwift) Test(ctx context.Context, h restic.Handle) (bool, error) {
   200  	objName := be.Filename(h)
   201  
   202  	be.sem.GetToken()
   203  	defer be.sem.ReleaseToken()
   204  
   205  	switch _, _, err := be.conn.Object(be.container, objName); err {
   206  	case nil:
   207  		return true, nil
   208  
   209  	case swift.ObjectNotFound:
   210  		return false, nil
   211  
   212  	default:
   213  		return false, errors.Wrap(err, "conn.Object")
   214  	}
   215  }
   216  
   217  // Remove removes the blob with the given name and type.
   218  func (be *beSwift) Remove(ctx context.Context, h restic.Handle) error {
   219  	objName := be.Filename(h)
   220  
   221  	be.sem.GetToken()
   222  	defer be.sem.ReleaseToken()
   223  
   224  	err := be.conn.ObjectDelete(be.container, objName)
   225  	debug.Log("Remove(%v) -> err %v", h, err)
   226  	return errors.Wrap(err, "conn.ObjectDelete")
   227  }
   228  
   229  // List runs fn for each file in the backend which has the type t. When an
   230  // error occurs (or fn returns an error), List stops and returns it.
   231  func (be *beSwift) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error {
   232  	debug.Log("listing %v", t)
   233  
   234  	prefix, _ := be.Basedir(t)
   235  	prefix += "/"
   236  
   237  	err := be.conn.ObjectsWalk(be.container, &swift.ObjectsOpts{Prefix: prefix},
   238  		func(opts *swift.ObjectsOpts) (interface{}, error) {
   239  			be.sem.GetToken()
   240  			newObjects, err := be.conn.Objects(be.container, opts)
   241  			be.sem.ReleaseToken()
   242  
   243  			if err != nil {
   244  				return nil, errors.Wrap(err, "conn.ObjectNames")
   245  			}
   246  			for _, obj := range newObjects {
   247  				m := path.Base(strings.TrimPrefix(obj.Name, prefix))
   248  				if m == "" {
   249  					continue
   250  				}
   251  
   252  				fi := restic.FileInfo{
   253  					Name: m,
   254  					Size: obj.Bytes,
   255  				}
   256  
   257  				if ctx.Err() != nil {
   258  					return nil, ctx.Err()
   259  				}
   260  
   261  				err := fn(fi)
   262  				if err != nil {
   263  					return nil, err
   264  				}
   265  
   266  				if ctx.Err() != nil {
   267  					return nil, ctx.Err()
   268  				}
   269  			}
   270  			return newObjects, nil
   271  		})
   272  
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	return ctx.Err()
   278  }
   279  
   280  // Remove keys for a specified backend type.
   281  func (be *beSwift) removeKeys(ctx context.Context, t restic.FileType) error {
   282  	return be.List(ctx, t, func(fi restic.FileInfo) error {
   283  		return be.Remove(ctx, restic.Handle{Type: t, Name: fi.Name})
   284  	})
   285  }
   286  
   287  // IsNotExist returns true if the error is caused by a not existing file.
   288  func (be *beSwift) IsNotExist(err error) bool {
   289  	if e, ok := errors.Cause(err).(*swift.Error); ok {
   290  		return e.StatusCode == http.StatusNotFound
   291  	}
   292  
   293  	return false
   294  }
   295  
   296  // Delete removes all restic objects in the container.
   297  // It will not remove the container itself.
   298  func (be *beSwift) Delete(ctx context.Context) error {
   299  	alltypes := []restic.FileType{
   300  		restic.DataFile,
   301  		restic.KeyFile,
   302  		restic.LockFile,
   303  		restic.SnapshotFile,
   304  		restic.IndexFile}
   305  
   306  	for _, t := range alltypes {
   307  		err := be.removeKeys(ctx, t)
   308  		if err != nil {
   309  			return nil
   310  		}
   311  	}
   312  
   313  	err := be.Remove(ctx, restic.Handle{Type: restic.ConfigFile})
   314  	if err != nil && !be.IsNotExist(err) {
   315  		return err
   316  	}
   317  
   318  	return nil
   319  }
   320  
   321  // Close does nothing
   322  func (be *beSwift) Close() error { return nil }