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 }