github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/storage/purgeuploads.go (about) 1 package storage 2 3 import ( 4 "path" 5 "strings" 6 "time" 7 8 log "github.com/Sirupsen/logrus" 9 "github.com/docker/distribution/context" 10 storageDriver "github.com/docker/distribution/registry/storage/driver" 11 "github.com/docker/distribution/uuid" 12 ) 13 14 // uploadData stored the location of temporary files created during a layer upload 15 // along with the date the upload was started 16 type uploadData struct { 17 containingDir string 18 startedAt time.Time 19 } 20 21 func newUploadData() uploadData { 22 return uploadData{ 23 containingDir: "", 24 // default to far in future to protect against missing startedat 25 startedAt: time.Now().Add(time.Duration(10000 * time.Hour)), 26 } 27 } 28 29 // PurgeUploads deletes files from the upload directory 30 // created before olderThan. The list of files deleted and errors 31 // encountered are returned 32 func PurgeUploads(ctx context.Context, driver storageDriver.StorageDriver, olderThan time.Time, actuallyDelete bool) ([]string, []error) { 33 log.Infof("PurgeUploads starting: olderThan=%s, actuallyDelete=%t", olderThan, actuallyDelete) 34 uploadData, errors := getOutstandingUploads(ctx, driver) 35 var deleted []string 36 for _, uploadData := range uploadData { 37 if uploadData.startedAt.Before(olderThan) { 38 var err error 39 log.Infof("Upload files in %s have older date (%s) than purge date (%s). Removing upload directory.", 40 uploadData.containingDir, uploadData.startedAt, olderThan) 41 if actuallyDelete { 42 err = driver.Delete(ctx, uploadData.containingDir) 43 } 44 if err == nil { 45 deleted = append(deleted, uploadData.containingDir) 46 } else { 47 errors = append(errors, err) 48 } 49 } 50 } 51 52 log.Infof("Purge uploads finished. Num deleted=%d, num errors=%d", len(deleted), len(errors)) 53 return deleted, errors 54 } 55 56 // getOutstandingUploads walks the upload directory, collecting files 57 // which could be eligible for deletion. The only reliable way to 58 // classify the age of a file is with the date stored in the startedAt 59 // file, so gather files by UUID with a date from startedAt. 60 func getOutstandingUploads(ctx context.Context, driver storageDriver.StorageDriver) (map[string]uploadData, []error) { 61 var errors []error 62 uploads := make(map[string]uploadData, 0) 63 64 inUploadDir := false 65 root, err := pathFor(repositoriesRootPathSpec{}) 66 if err != nil { 67 return uploads, append(errors, err) 68 } 69 70 err = Walk(ctx, driver, root, func(fileInfo storageDriver.FileInfo) error { 71 filePath := fileInfo.Path() 72 _, file := path.Split(filePath) 73 if file[0] == '_' { 74 // Reserved directory 75 inUploadDir = (file == "_uploads") 76 77 if fileInfo.IsDir() && !inUploadDir { 78 return ErrSkipDir 79 } 80 81 } 82 83 uuid, isContainingDir := uUIDFromPath(filePath) 84 if uuid == "" { 85 // Cannot reliably delete 86 return nil 87 } 88 ud, ok := uploads[uuid] 89 if !ok { 90 ud = newUploadData() 91 } 92 if isContainingDir { 93 ud.containingDir = filePath 94 } 95 if file == "startedat" { 96 if t, err := readStartedAtFile(driver, filePath); err == nil { 97 ud.startedAt = t 98 } else { 99 errors = pushError(errors, filePath, err) 100 } 101 102 } 103 104 uploads[uuid] = ud 105 return nil 106 }) 107 108 if err != nil { 109 errors = pushError(errors, root, err) 110 } 111 return uploads, errors 112 } 113 114 // uUIDFromPath extracts the upload UUID from a given path 115 // If the UUID is the last path component, this is the containing 116 // directory for all upload files 117 func uUIDFromPath(path string) (string, bool) { 118 components := strings.Split(path, "/") 119 for i := len(components) - 1; i >= 0; i-- { 120 if u, err := uuid.Parse(components[i]); err == nil { 121 return u.String(), i == len(components)-1 122 } 123 } 124 return "", false 125 } 126 127 // readStartedAtFile reads the date from an upload's startedAtFile 128 func readStartedAtFile(driver storageDriver.StorageDriver, path string) (time.Time, error) { 129 // todo:(richardscothern) - pass in a context 130 startedAtBytes, err := driver.GetContent(context.Background(), path) 131 if err != nil { 132 return time.Now(), err 133 } 134 startedAt, err := time.Parse(time.RFC3339, string(startedAtBytes)) 135 if err != nil { 136 return time.Now(), err 137 } 138 return startedAt, nil 139 }