Merge pull request #55 from BrianBland/layerhandler
Adds support for content redirects for layer downloadsmaster
						commit
						e8714b9977
					
				| 
						 | 
				
			
			@ -24,6 +24,9 @@ type Configuration struct {
 | 
			
		|||
	// used to gate requests.
 | 
			
		||||
	Auth Auth `yaml:"auth"`
 | 
			
		||||
 | 
			
		||||
	// LayerHandler specifies a middleware for serving image layers.
 | 
			
		||||
	LayerHandler LayerHandler `yaml:"layerhandler"`
 | 
			
		||||
 | 
			
		||||
	// Reporting is the configuration for error reporting
 | 
			
		||||
	Reporting Reporting `yaml:"reporting"`
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -240,6 +243,62 @@ type NewRelicReporting struct {
 | 
			
		|||
	Name string `yaml:"name"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LayerHandler defines the configuration for middleware layer serving
 | 
			
		||||
type LayerHandler map[string]Parameters
 | 
			
		||||
 | 
			
		||||
// Type returns the layerhandler type
 | 
			
		||||
func (layerHandler LayerHandler) Type() string {
 | 
			
		||||
	// Return only key in this map
 | 
			
		||||
	for k := range layerHandler {
 | 
			
		||||
		return k
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parameters returns the Parameters map for a LayerHandler configuration
 | 
			
		||||
func (layerHandler LayerHandler) Parameters() Parameters {
 | 
			
		||||
	return layerHandler[layerHandler.Type()]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnmarshalYAML implements the yaml.Unmarshaler interface
 | 
			
		||||
// Unmarshals a single item map into a Storage or a string into a Storage type with no parameters
 | 
			
		||||
func (layerHandler *LayerHandler) UnmarshalYAML(unmarshal func(interface{}) error) error {
 | 
			
		||||
	var storageMap map[string]Parameters
 | 
			
		||||
	err := unmarshal(&storageMap)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		if len(storageMap) > 1 {
 | 
			
		||||
			types := make([]string, 0, len(storageMap))
 | 
			
		||||
			for k := range storageMap {
 | 
			
		||||
				types = append(types, k)
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("Must provide exactly one layerhandler type. Provided: %v", types)
 | 
			
		||||
		}
 | 
			
		||||
		*layerHandler = storageMap
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var storageType string
 | 
			
		||||
	err = unmarshal(&storageType)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		*layerHandler = LayerHandler{storageType: Parameters{}}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalYAML implements the yaml.Marshaler interface
 | 
			
		||||
func (layerHandler LayerHandler) MarshalYAML() (interface{}, error) {
 | 
			
		||||
	if layerHandler.Parameters() == nil {
 | 
			
		||||
		t := layerHandler.Type()
 | 
			
		||||
		if t == "" {
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		return t, nil
 | 
			
		||||
	}
 | 
			
		||||
	return map[string]Parameters(layerHandler), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse parses an input configuration yaml document into a Configuration struct
 | 
			
		||||
// This should generally be capable of handling old configuration format versions
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,8 @@ type App struct {
 | 
			
		|||
 | 
			
		||||
	tokenProvider tokenProvider
 | 
			
		||||
 | 
			
		||||
	layerHandler storage.LayerHandler
 | 
			
		||||
 | 
			
		||||
	accessController auth.AccessController
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +78,16 @@ func NewApp(configuration configuration.Configuration) *App {
 | 
			
		|||
		app.accessController = accessController
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	layerHandlerType := configuration.LayerHandler.Type()
 | 
			
		||||
 | 
			
		||||
	if layerHandlerType != "" {
 | 
			
		||||
		lh, err := storage.GetLayerHandler(layerHandlerType, configuration.LayerHandler.Parameters(), driver)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(fmt.Sprintf("unable to configure layer handler (%s): %v", layerHandlerType, err))
 | 
			
		||||
		}
 | 
			
		||||
		app.layerHandler = lh
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return app
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,5 +58,13 @@ func (lh *layerHandler) GetLayer(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
	}
 | 
			
		||||
	defer layer.Close()
 | 
			
		||||
 | 
			
		||||
	if lh.layerHandler != nil {
 | 
			
		||||
		handler, _ := lh.layerHandler.Resolve(layer)
 | 
			
		||||
		if handler != nil {
 | 
			
		||||
			handler.ServeHTTP(w, r)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.ServeContent(w, r, layer.Digest().String(), layer.CreatedAt(), layer)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,106 @@
 | 
			
		|||
package storage
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/pem"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/crowdmob/goamz/cloudfront"
 | 
			
		||||
	"github.com/docker/distribution/storagedriver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// cloudFrontLayerHandler provides an simple implementation of layerHandler that
 | 
			
		||||
// constructs temporary signed CloudFront URLs from the storagedriver layer URL,
 | 
			
		||||
// then issues HTTP Temporary Redirects to this CloudFront content URL.
 | 
			
		||||
type cloudFrontLayerHandler struct {
 | 
			
		||||
	cloudfront           *cloudfront.CloudFront
 | 
			
		||||
	delegateLayerHandler *delegateLayerHandler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ LayerHandler = &cloudFrontLayerHandler{}
 | 
			
		||||
 | 
			
		||||
// newCloudFrontLayerHandler constructs and returns a new CloudFront
 | 
			
		||||
// LayerHandler implementation.
 | 
			
		||||
// Required options: baseurl, privatekey, keypairid
 | 
			
		||||
func newCloudFrontLayerHandler(storageDriver storagedriver.StorageDriver, options map[string]interface{}) (LayerHandler, error) {
 | 
			
		||||
	base, ok := options["baseurl"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("No baseurl provided")
 | 
			
		||||
	}
 | 
			
		||||
	baseURL, ok := base.(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("baseurl must be a string")
 | 
			
		||||
	}
 | 
			
		||||
	pk, ok := options["privatekey"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("No privatekey provided")
 | 
			
		||||
	}
 | 
			
		||||
	pkPath, ok := pk.(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("privatekey must be a string")
 | 
			
		||||
	}
 | 
			
		||||
	kpid, ok := options["keypairid"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("No keypairid provided")
 | 
			
		||||
	}
 | 
			
		||||
	keypairID, ok := kpid.(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("keypairid must be a string")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pkBytes, err := ioutil.ReadFile(pkPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to read privatekey file: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	block, _ := pem.Decode([]byte(pkBytes))
 | 
			
		||||
	if block == nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to decode private key as an rsa private key")
 | 
			
		||||
	}
 | 
			
		||||
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lh, err := newDelegateLayerHandler(storageDriver, options)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	dlh := lh.(*delegateLayerHandler)
 | 
			
		||||
 | 
			
		||||
	cf := cloudfront.New(baseURL, privateKey, keypairID)
 | 
			
		||||
 | 
			
		||||
	return &cloudFrontLayerHandler{cloudfront: cf, delegateLayerHandler: dlh}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Resolve returns an http.Handler which can serve the contents of the given
 | 
			
		||||
// Layer, or an error if not supported by the storagedriver.
 | 
			
		||||
func (lh *cloudFrontLayerHandler) Resolve(layer Layer) (http.Handler, error) {
 | 
			
		||||
	layerURLStr, err := lh.delegateLayerHandler.urlFor(layer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	layerURL, err := url.Parse(layerURLStr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfURL, err := lh.cloudfront.CannedSignedURL(layerURL.Path, "", time.Now().Add(20*time.Minute))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		http.Redirect(w, r, cfURL, http.StatusTemporaryRedirect)
 | 
			
		||||
	}), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// init registers the cloudfront layerHandler backend.
 | 
			
		||||
func init() {
 | 
			
		||||
	RegisterLayerHandler("cloudfront", LayerHandlerInitFunc(newCloudFrontLayerHandler))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,73 @@
 | 
			
		|||
package storage
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/distribution/storagedriver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// delegateLayerHandler provides a simple implementation of layerHandler that
 | 
			
		||||
// simply issues HTTP Temporary Redirects to the URL provided by the
 | 
			
		||||
// storagedriver for a given Layer.
 | 
			
		||||
type delegateLayerHandler struct {
 | 
			
		||||
	storageDriver storagedriver.StorageDriver
 | 
			
		||||
	pathMapper    *pathMapper
 | 
			
		||||
	duration      time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ LayerHandler = &delegateLayerHandler{}
 | 
			
		||||
 | 
			
		||||
func newDelegateLayerHandler(storageDriver storagedriver.StorageDriver, options map[string]interface{}) (LayerHandler, error) {
 | 
			
		||||
	duration := 20 * time.Minute
 | 
			
		||||
	d, ok := options["duration"]
 | 
			
		||||
	if ok {
 | 
			
		||||
		switch d := d.(type) {
 | 
			
		||||
		case time.Duration:
 | 
			
		||||
			duration = d
 | 
			
		||||
		case string:
 | 
			
		||||
			dur, err := time.ParseDuration(d)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf("Invalid duration: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			duration = dur
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &delegateLayerHandler{storageDriver: storageDriver, pathMapper: defaultPathMapper, duration: duration}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Resolve returns an http.Handler which can serve the contents of the given
 | 
			
		||||
// Layer, or an error if not supported by the storagedriver.
 | 
			
		||||
func (lh *delegateLayerHandler) Resolve(layer Layer) (http.Handler, error) {
 | 
			
		||||
	layerURL, err := lh.urlFor(layer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		http.Redirect(w, r, layerURL, http.StatusTemporaryRedirect)
 | 
			
		||||
	}), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// urlFor returns a download URL for the given layer, or the empty string if
 | 
			
		||||
// unsupported.
 | 
			
		||||
func (lh *delegateLayerHandler) urlFor(layer Layer) (string, error) {
 | 
			
		||||
	blobPath, err := resolveBlobPath(lh.storageDriver, lh.pathMapper, layer.Name(), layer.Digest())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	layerURL, err := lh.storageDriver.URLFor(blobPath, map[string]interface{}{"expiry": time.Now().Add(lh.duration)})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return layerURL, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// init registers the delegate layerHandler backend.
 | 
			
		||||
func init() {
 | 
			
		||||
	RegisterLayerHandler("delegate", LayerHandlerInitFunc(newDelegateLayerHandler))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
package storage
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/distribution/storagedriver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LayerHandler provides middleware for serving the contents of a Layer.
 | 
			
		||||
type LayerHandler interface {
 | 
			
		||||
	// Resolve returns an http.Handler which can serve the contents of a given
 | 
			
		||||
	// Layer if possible, or nil and an error when unsupported. This may
 | 
			
		||||
	// directly serve the contents of the layer or issue a redirect to another
 | 
			
		||||
	// URL hosting the content.
 | 
			
		||||
	Resolve(layer Layer) (http.Handler, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LayerHandlerInitFunc is the type of a LayerHandler factory function and is
 | 
			
		||||
// used to register the contsructor for different LayerHandler backends.
 | 
			
		||||
type LayerHandlerInitFunc func(storageDriver storagedriver.StorageDriver, options map[string]interface{}) (LayerHandler, error)
 | 
			
		||||
 | 
			
		||||
var layerHandlers map[string]LayerHandlerInitFunc
 | 
			
		||||
 | 
			
		||||
// RegisterLayerHandler is used to register an LayerHandlerInitFunc for
 | 
			
		||||
// a LayerHandler backend with the given name.
 | 
			
		||||
func RegisterLayerHandler(name string, initFunc LayerHandlerInitFunc) error {
 | 
			
		||||
	if layerHandlers == nil {
 | 
			
		||||
		layerHandlers = make(map[string]LayerHandlerInitFunc)
 | 
			
		||||
	}
 | 
			
		||||
	if _, exists := layerHandlers[name]; exists {
 | 
			
		||||
		return fmt.Errorf("name already registered: %s", name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	layerHandlers[name] = initFunc
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLayerHandler constructs a LayerHandler
 | 
			
		||||
// with the given options using the named backend.
 | 
			
		||||
func GetLayerHandler(name string, options map[string]interface{}, storageDriver storagedriver.StorageDriver) (LayerHandler, error) {
 | 
			
		||||
	if layerHandlers != nil {
 | 
			
		||||
		if initFunc, exists := layerHandlers[name]; exists {
 | 
			
		||||
			return initFunc(storageDriver, options)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, fmt.Errorf("no layer handler registered with name: %s", name)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ func (ls *layerStore) Exists(name string, digest digest.Digest) (bool, error) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func (ls *layerStore) Fetch(name string, digest digest.Digest) (Layer, error) {
 | 
			
		||||
	blobPath, err := ls.resolveBlobPath(name, digest)
 | 
			
		||||
	blobPath, err := resolveBlobPath(ls.driver, ls.pathMapper, name, digest)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		switch err := err.(type) {
 | 
			
		||||
		case storagedriver.PathNotFoundError, *storagedriver.PathNotFoundError:
 | 
			
		||||
| 
						 | 
				
			
			@ -94,31 +94,3 @@ func (ls *layerStore) newLayerUpload(lus LayerUploadState) LayerUpload {
 | 
			
		|||
		uploadStore:      ls.uploadStore,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// resolveBlobId looks up the blob location in the repositories from a
 | 
			
		||||
// layer/blob link file, returning blob path or an error on failure.
 | 
			
		||||
func (ls *layerStore) resolveBlobPath(name string, dgst digest.Digest) (string, error) {
 | 
			
		||||
	pathSpec := layerLinkPathSpec{name: name, digest: dgst}
 | 
			
		||||
	layerLinkPath, err := ls.pathMapper.path(pathSpec)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	layerLinkContent, err := ls.driver.GetContent(layerLinkPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// NOTE(stevvooe): The content of the layer link should match the digest.
 | 
			
		||||
	// This layer of indirection is for name-based content protection.
 | 
			
		||||
 | 
			
		||||
	linked, err := digest.ParseDigest(string(layerLinkContent))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bp := blobPathSpec{digest: linked}
 | 
			
		||||
 | 
			
		||||
	return ls.pathMapper.path(bp)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ import (
 | 
			
		|||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/distribution/digest"
 | 
			
		||||
	"github.com/docker/distribution/storagedriver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const storagePathVersion = "v2"
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +45,11 @@ type pathMapper struct {
 | 
			
		|||
	version string // should be a constant?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var defaultPathMapper = &pathMapper{
 | 
			
		||||
	root:    "/docker/registry/",
 | 
			
		||||
	version: storagePathVersion,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// path returns the path identified by spec.
 | 
			
		||||
func (pm *pathMapper) path(spec pathSpec) (string, error) {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -204,3 +210,31 @@ func digestPathComoponents(dgst digest.Digest) ([]string, error) {
 | 
			
		|||
 | 
			
		||||
	return append(prefix, suffix...), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// resolveBlobPath looks up the blob location in the repositories from a
 | 
			
		||||
// layer/blob link file, returning blob path or an error on failure.
 | 
			
		||||
func resolveBlobPath(driver storagedriver.StorageDriver, pm *pathMapper, name string, dgst digest.Digest) (string, error) {
 | 
			
		||||
	pathSpec := layerLinkPathSpec{name: name, digest: dgst}
 | 
			
		||||
	layerLinkPath, err := pm.path(pathSpec)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	layerLinkContent, err := driver.GetContent(layerLinkPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// NOTE(stevvooe): The content of the layer link should match the digest.
 | 
			
		||||
	// This layer of indirection is for name-based content protection.
 | 
			
		||||
 | 
			
		||||
	linked, err := digest.ParseDigest(string(layerLinkContent))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bp := blobPathSpec{digest: linked}
 | 
			
		||||
 | 
			
		||||
	return pm.path(bp)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,11 +28,8 @@ func NewServices(driver storagedriver.StorageDriver) *Services {
 | 
			
		|||
 | 
			
		||||
	return &Services{
 | 
			
		||||
		driver: driver,
 | 
			
		||||
		pathMapper: &pathMapper{
 | 
			
		||||
			// TODO(sday): This should be configurable.
 | 
			
		||||
			root:    "/docker/registry/",
 | 
			
		||||
			version: storagePathVersion,
 | 
			
		||||
		},
 | 
			
		||||
		// TODO(sday): This should be configurable.
 | 
			
		||||
		pathMapper:       defaultPathMapper,
 | 
			
		||||
		layerUploadStore: layerUploadStore,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -267,7 +267,7 @@ func (d *Driver) Delete(subPath string) error {
 | 
			
		|||
 | 
			
		||||
// URLFor returns a URL which may be used to retrieve the content stored at the given path.
 | 
			
		||||
// May return an UnsupportedMethodErr in certain StorageDriver implementations.
 | 
			
		||||
func (d *Driver) URLFor(path string) (string, error) {
 | 
			
		||||
func (d *Driver) URLFor(path string, options map[string]interface{}) (string, error) {
 | 
			
		||||
	return "", storagedriver.ErrUnsupportedMethod
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -254,6 +254,6 @@ func (d *Driver) Delete(path string) error {
 | 
			
		|||
 | 
			
		||||
// URLFor returns a URL which may be used to retrieve the content stored at the given path.
 | 
			
		||||
// May return an UnsupportedMethodErr in certain StorageDriver implementations.
 | 
			
		||||
func (d *Driver) URLFor(path string) (string, error) {
 | 
			
		||||
func (d *Driver) URLFor(path string, options map[string]interface{}) (string, error) {
 | 
			
		||||
	return "", storagedriver.ErrUnsupportedMethod
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -582,12 +582,21 @@ func (d *Driver) Delete(path string) error {
 | 
			
		|||
 | 
			
		||||
// URLFor returns a URL which may be used to retrieve the content stored at the given path.
 | 
			
		||||
// May return an UnsupportedMethodErr in certain StorageDriver implementations.
 | 
			
		||||
func (d *Driver) URLFor(path string) (string, error) {
 | 
			
		||||
func (d *Driver) URLFor(path string, options map[string]interface{}) (string, error) {
 | 
			
		||||
	if !storagedriver.PathRegexp.MatchString(path) {
 | 
			
		||||
		return "", storagedriver.InvalidPathError{Path: path}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return d.Bucket.SignedURL(d.s3Path(path), time.Now().Add(24*time.Hour)), nil
 | 
			
		||||
	expiresTime := time.Now().Add(20 * time.Minute)
 | 
			
		||||
	expires, ok := options["expiry"]
 | 
			
		||||
	if ok {
 | 
			
		||||
		et, ok := expires.(time.Time)
 | 
			
		||||
		if ok {
 | 
			
		||||
			expiresTime = et
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return d.Bucket.SignedURL(d.s3Path(path), expiresTime), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Driver) s3Path(path string) string {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,9 +71,11 @@ type StorageDriver interface {
 | 
			
		|||
	// Delete recursively deletes all objects stored at "path" and its subpaths.
 | 
			
		||||
	Delete(path string) error
 | 
			
		||||
 | 
			
		||||
	// URLFor returns a URL which may be used to retrieve the content stored at the given path.
 | 
			
		||||
	// May return an UnsupportedMethodErr in certain StorageDriver implementations.
 | 
			
		||||
	URLFor(path string) (string, error)
 | 
			
		||||
	// URLFor returns a URL which may be used to retrieve the content stored at
 | 
			
		||||
	// the given path, possibly using the given options.
 | 
			
		||||
	// May return an UnsupportedMethodErr in certain StorageDriver
 | 
			
		||||
	// implementations.
 | 
			
		||||
	URLFor(path string, options map[string]interface{}) (string, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PathRegexp is the regular expression which each file path must match.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -592,7 +592,7 @@ func (suite *DriverSuite) TestURLFor(c *check.C) {
 | 
			
		|||
	err := suite.StorageDriver.PutContent(filename, contents)
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
 | 
			
		||||
	url, err := suite.StorageDriver.URLFor(filename)
 | 
			
		||||
	url, err := suite.StorageDriver.URLFor(filename, nil)
 | 
			
		||||
	if err == storagedriver.ErrUnsupportedMethod {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue