package storage import ( "fmt" "net/http" "time" "github.com/docker/distribution" storagedriver "github.com/docker/distribution/registry/storage/driver" ) // 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 distribution.Layer) (http.Handler, error) { // TODO(bbland): This is just a sanity check to ensure that the // storagedriver supports url generation. It would be nice if we didn't have // to do this twice for non-GET requests. layerURL, err := lh.urlFor(layer, map[string]interface{}{"method": "GET"}) if err != nil { return nil, err } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { layerURL, err = lh.urlFor(layer, map[string]interface{}{"method": r.Method}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } 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 distribution.Layer, options map[string]interface{}) (string, error) { // Crack open the layer to get at the layerStore layerRd, ok := layer.(*layerReader) if !ok { // TODO(stevvooe): We probably want to find a better way to get at the // underlying filesystem path for a given layer. Perhaps, the layer // handler should have its own layer store but right now, it is not // request scoped. return "", fmt.Errorf("unsupported layer type: cannot resolve blob path: %v", layer) } if options == nil { options = make(map[string]interface{}) } options["expiry"] = time.Now().Add(lh.duration) layerURL, err := lh.storageDriver.URLFor(layerRd.path, options) if err != nil { return "", err } return layerURL, nil } // init registers the delegate layerHandler backend. func init() { RegisterLayerHandler("delegate", LayerHandlerInitFunc(newDelegateLayerHandler)) }