github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/resource/api/server/upload.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package server
     5  
     6  import (
     7  	"io"
     8  	"net/http"
     9  	"path"
    10  
    11  	"github.com/juju/errors"
    12  	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
    13  
    14  	"github.com/juju/juju/resource"
    15  	"github.com/juju/juju/resource/api"
    16  )
    17  
    18  // UploadDataStore describes the the portion of Juju's "state"
    19  // needed for handling upload requests.
    20  type UploadDataStore interface {
    21  	// GetResource returns the identified resource.
    22  	GetResource(serviceID, name string) (resource.Resource, error)
    23  
    24  	// GetPendingResource returns the identified resource.
    25  	GetPendingResource(serviceID, name, pendingID string) (resource.Resource, error)
    26  
    27  	// SetResource adds the resource to blob storage and updates the metadata.
    28  	SetResource(serviceID, userID string, res charmresource.Resource, r io.Reader) (resource.Resource, error)
    29  
    30  	// UpdatePendingResource adds the resource to blob storage and updates the metadata.
    31  	UpdatePendingResource(serviceID, pendingID, userID string, res charmresource.Resource, r io.Reader) (resource.Resource, error)
    32  }
    33  
    34  // TODO(ericsnow) Replace UploadedResource with resource.Opened.
    35  
    36  // UploadedResource holds both the information about an uploaded
    37  // resource and the reader containing its data.
    38  type UploadedResource struct {
    39  	// Service is the name of the service associated with the resource.
    40  	Service string
    41  
    42  	// PendingID is the resource-specific sub-ID for a pending resource.
    43  	PendingID string
    44  
    45  	// Resource is the information about the resource.
    46  	Resource charmresource.Resource
    47  
    48  	// Data holds the resource blob.
    49  	Data io.ReadCloser
    50  }
    51  
    52  // UploadHandler provides the functionality to handle upload requests.
    53  type UploadHandler struct {
    54  	// Username is the ID of the user making the upload request.
    55  	Username string
    56  
    57  	// Store is the data store into which the resource will be stored.
    58  	Store UploadDataStore
    59  }
    60  
    61  // HandleRequest handles a resource upload request.
    62  func (uh UploadHandler) HandleRequest(req *http.Request) (*api.UploadResult, error) {
    63  	defer req.Body.Close()
    64  
    65  	uploaded, err := uh.ReadResource(req)
    66  	if err != nil {
    67  		return nil, errors.Trace(err)
    68  	}
    69  
    70  	var stored resource.Resource
    71  	if uploaded.PendingID != "" {
    72  		stored, err = uh.Store.UpdatePendingResource(uploaded.Service, uploaded.PendingID, uh.Username, uploaded.Resource, uploaded.Data)
    73  		if err != nil {
    74  			return nil, errors.Trace(err)
    75  		}
    76  	} else {
    77  		stored, err = uh.Store.SetResource(uploaded.Service, uh.Username, uploaded.Resource, uploaded.Data)
    78  		if err != nil {
    79  			return nil, errors.Trace(err)
    80  		}
    81  	}
    82  
    83  	result := &api.UploadResult{
    84  		Resource: api.Resource2API(stored),
    85  	}
    86  	return result, nil
    87  }
    88  
    89  // ReadResource extracts the relevant info from the request.
    90  func (uh UploadHandler) ReadResource(req *http.Request) (*UploadedResource, error) {
    91  	uReq, err := api.ExtractUploadRequest(req)
    92  	if err != nil {
    93  		return nil, errors.Trace(err)
    94  	}
    95  	var res resource.Resource
    96  	if uReq.PendingID != "" {
    97  		res, err = uh.Store.GetPendingResource(uReq.Service, uReq.Name, uReq.PendingID)
    98  		if err != nil {
    99  			return nil, errors.Trace(err)
   100  		}
   101  	} else {
   102  		res, err = uh.Store.GetResource(uReq.Service, uReq.Name)
   103  		if err != nil {
   104  			return nil, errors.Trace(err)
   105  		}
   106  	}
   107  
   108  	ext := path.Ext(res.Path)
   109  	if path.Ext(uReq.Filename) != ext {
   110  		return nil, errors.Errorf("incorrect extension on resource upload %q, expected %q", uReq.Filename, ext)
   111  	}
   112  
   113  	chRes, err := uh.updateResource(res.Resource, uReq.Fingerprint, uReq.Size)
   114  	if err != nil {
   115  		return nil, errors.Trace(err)
   116  	}
   117  
   118  	uploaded := &UploadedResource{
   119  		Service:   uReq.Service,
   120  		PendingID: uReq.PendingID,
   121  		Resource:  chRes,
   122  		Data:      req.Body,
   123  	}
   124  	return uploaded, nil
   125  }
   126  
   127  // updateResource returns a copy of the provided resource, updated with
   128  // the given information.
   129  func (uh UploadHandler) updateResource(res charmresource.Resource, fp charmresource.Fingerprint, size int64) (charmresource.Resource, error) {
   130  	res.Origin = charmresource.OriginUpload
   131  	res.Revision = 0
   132  	res.Fingerprint = fp
   133  	res.Size = size
   134  
   135  	if err := res.Validate(); err != nil {
   136  		return res, errors.Trace(err)
   137  	}
   138  	return res, nil
   139  }