139 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			139 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
package storage
 | 
						|
 | 
						|
import (
 | 
						|
	"path"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	log "github.com/Sirupsen/logrus"
 | 
						|
	"github.com/docker/distribution/context"
 | 
						|
	storageDriver "github.com/docker/distribution/registry/storage/driver"
 | 
						|
	"github.com/docker/distribution/uuid"
 | 
						|
)
 | 
						|
 | 
						|
// uploadData stored the location of temporary files created during a layer upload
 | 
						|
// along with the date the upload was started
 | 
						|
type uploadData struct {
 | 
						|
	containingDir string
 | 
						|
	startedAt     time.Time
 | 
						|
}
 | 
						|
 | 
						|
func newUploadData() uploadData {
 | 
						|
	return uploadData{
 | 
						|
		containingDir: "",
 | 
						|
		// default to far in future to protect against missing startedat
 | 
						|
		startedAt: time.Now().Add(time.Duration(10000 * time.Hour)),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// PurgeUploads deletes files from the upload directory
 | 
						|
// created before olderThan.  The list of files deleted and errors
 | 
						|
// encountered are returned
 | 
						|
func PurgeUploads(ctx context.Context, driver storageDriver.StorageDriver, olderThan time.Time, actuallyDelete bool) ([]string, []error) {
 | 
						|
	log.Infof("PurgeUploads starting: olderThan=%s, actuallyDelete=%t", olderThan, actuallyDelete)
 | 
						|
	uploadData, errors := getOutstandingUploads(ctx, driver)
 | 
						|
	var deleted []string
 | 
						|
	for _, uploadData := range uploadData {
 | 
						|
		if uploadData.startedAt.Before(olderThan) {
 | 
						|
			var err error
 | 
						|
			log.Infof("Upload files in %s have older date (%s) than purge date (%s).  Removing upload directory.",
 | 
						|
				uploadData.containingDir, uploadData.startedAt, olderThan)
 | 
						|
			if actuallyDelete {
 | 
						|
				err = driver.Delete(ctx, uploadData.containingDir)
 | 
						|
			}
 | 
						|
			if err == nil {
 | 
						|
				deleted = append(deleted, uploadData.containingDir)
 | 
						|
			} else {
 | 
						|
				errors = append(errors, err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	log.Infof("Purge uploads finished.  Num deleted=%d, num errors=%d", len(deleted), len(errors))
 | 
						|
	return deleted, errors
 | 
						|
}
 | 
						|
 | 
						|
// getOutstandingUploads walks the upload directory, collecting files
 | 
						|
// which could be eligible for deletion.  The only reliable way to
 | 
						|
// classify the age of a file is with the date stored in the startedAt
 | 
						|
// file, so gather files by UUID with a date from startedAt.
 | 
						|
func getOutstandingUploads(ctx context.Context, driver storageDriver.StorageDriver) (map[string]uploadData, []error) {
 | 
						|
	var errors []error
 | 
						|
	uploads := make(map[string]uploadData, 0)
 | 
						|
 | 
						|
	inUploadDir := false
 | 
						|
	root, err := defaultPathMapper.path(repositoriesRootPathSpec{})
 | 
						|
	if err != nil {
 | 
						|
		return uploads, append(errors, err)
 | 
						|
	}
 | 
						|
	err = Walk(ctx, driver, root, func(fileInfo storageDriver.FileInfo) error {
 | 
						|
		filePath := fileInfo.Path()
 | 
						|
		_, file := path.Split(filePath)
 | 
						|
		if file[0] == '_' {
 | 
						|
			// Reserved directory
 | 
						|
			inUploadDir = (file == "_uploads")
 | 
						|
 | 
						|
			if fileInfo.IsDir() && !inUploadDir {
 | 
						|
				return ErrSkipDir
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
 | 
						|
		uuid, isContainingDir := uUIDFromPath(filePath)
 | 
						|
		if uuid == "" {
 | 
						|
			// Cannot reliably delete
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		ud, ok := uploads[uuid]
 | 
						|
		if !ok {
 | 
						|
			ud = newUploadData()
 | 
						|
		}
 | 
						|
		if isContainingDir {
 | 
						|
			ud.containingDir = filePath
 | 
						|
		}
 | 
						|
		if file == "startedat" {
 | 
						|
			if t, err := readStartedAtFile(driver, filePath); err == nil {
 | 
						|
				ud.startedAt = t
 | 
						|
			} else {
 | 
						|
				errors = pushError(errors, filePath, err)
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
 | 
						|
		uploads[uuid] = ud
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		errors = pushError(errors, root, err)
 | 
						|
	}
 | 
						|
	return uploads, errors
 | 
						|
}
 | 
						|
 | 
						|
// uUIDFromPath extracts the upload UUID from a given path
 | 
						|
// If the UUID is the last path component, this is the containing
 | 
						|
// directory for all upload files
 | 
						|
func uUIDFromPath(path string) (string, bool) {
 | 
						|
	components := strings.Split(path, "/")
 | 
						|
	for i := len(components) - 1; i >= 0; i-- {
 | 
						|
		if u, err := uuid.Parse(components[i]); err == nil {
 | 
						|
			return u.String(), i == len(components)-1
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return "", false
 | 
						|
}
 | 
						|
 | 
						|
// readStartedAtFile reads the date from an upload's startedAtFile
 | 
						|
func readStartedAtFile(driver storageDriver.StorageDriver, path string) (time.Time, error) {
 | 
						|
	// todo:(richardscothern) - pass in a context
 | 
						|
	startedAtBytes, err := driver.GetContent(context.Background(), path)
 | 
						|
	if err != nil {
 | 
						|
		return time.Now(), err
 | 
						|
	}
 | 
						|
	startedAt, err := time.Parse(time.RFC3339, string(startedAtBytes))
 | 
						|
	if err != nil {
 | 
						|
		return time.Now(), err
 | 
						|
	}
 | 
						|
	return startedAt, nil
 | 
						|
}
 |