github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/resource/api/client/client.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client
     5  
     6  import (
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
    13  	"gopkg.in/macaroon.v1"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/charmstore"
    17  	"github.com/juju/juju/resource"
    18  	"github.com/juju/juju/resource/api"
    19  )
    20  
    21  // TODO(ericsnow) Move FacadeCaller to a component-central package.
    22  
    23  // FacadeCaller has the api/base.FacadeCaller methods needed for the component.
    24  type FacadeCaller interface {
    25  	FacadeCall(request string, params, response interface{}) error
    26  }
    27  
    28  // Doer
    29  type Doer interface {
    30  	Do(req *http.Request, body io.ReadSeeker, resp interface{}) error
    31  }
    32  
    33  // Client is the public client for the resources API facade.
    34  type Client struct {
    35  	FacadeCaller
    36  	io.Closer
    37  	doer Doer
    38  }
    39  
    40  // NewClient returns a new Client for the given raw API caller.
    41  func NewClient(caller FacadeCaller, doer Doer, closer io.Closer) *Client {
    42  	return &Client{
    43  		FacadeCaller: caller,
    44  		Closer:       closer,
    45  		doer:         doer,
    46  	}
    47  }
    48  
    49  // ListResources calls the ListResources API server method with
    50  // the given application names.
    51  func (c Client) ListResources(services []string) ([]resource.ServiceResources, error) {
    52  	args, err := api.NewListResourcesArgs(services)
    53  	if err != nil {
    54  		return nil, errors.Trace(err)
    55  	}
    56  
    57  	var apiResults api.ResourcesResults
    58  	if err := c.FacadeCall("ListResources", &args, &apiResults); err != nil {
    59  		return nil, errors.Trace(err)
    60  	}
    61  
    62  	if len(apiResults.Results) != len(services) {
    63  		// We don't bother returning the results we *did* get since
    64  		// something bad happened on the server.
    65  		return nil, errors.Errorf("got invalid data from server (expected %d results, got %d)", len(services), len(apiResults.Results))
    66  	}
    67  
    68  	var errs []error
    69  	results := make([]resource.ServiceResources, len(services))
    70  	for i := range services {
    71  		apiResult := apiResults.Results[i]
    72  
    73  		result, err := api.APIResult2ServiceResources(apiResult)
    74  		if err != nil {
    75  			errs = append(errs, errors.Trace(err))
    76  		}
    77  		results[i] = result
    78  	}
    79  	if err := resolveErrors(errs); err != nil {
    80  		return nil, errors.Trace(err)
    81  	}
    82  
    83  	return results, nil
    84  }
    85  
    86  // Upload sends the provided resource blob up to Juju.
    87  func (c Client) Upload(service, name, filename string, reader io.ReadSeeker) error {
    88  	uReq, err := api.NewUploadRequest(service, name, filename, reader)
    89  	if err != nil {
    90  		return errors.Trace(err)
    91  	}
    92  	req, err := uReq.HTTPRequest()
    93  	if err != nil {
    94  		return errors.Trace(err)
    95  	}
    96  
    97  	var response api.UploadResult // ignored
    98  	if err := c.doer.Do(req, reader, &response); err != nil {
    99  		return errors.Trace(err)
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // AddPendingResourcesArgs holds the arguments to AddPendingResources().
   106  type AddPendingResourcesArgs struct {
   107  	// ApplicationID identifies the application being deployed.
   108  	ApplicationID string
   109  
   110  	// CharmID identifies the application's charm.
   111  	CharmID charmstore.CharmID
   112  
   113  	// CharmStoreMacaroon is the macaroon to use for the charm when
   114  	// interacting with the charm store.
   115  	CharmStoreMacaroon *macaroon.Macaroon
   116  
   117  	// Resources holds the charm store info for each of the resources
   118  	// that should be added/updated on the controller.
   119  	Resources []charmresource.Resource
   120  }
   121  
   122  // AddPendingResources sends the provided resource info up to Juju
   123  // without making it available yet.
   124  func (c Client) AddPendingResources(args AddPendingResourcesArgs) (pendingIDs []string, err error) {
   125  	apiArgs, err := api.NewAddPendingResourcesArgs(args.ApplicationID, args.CharmID, args.CharmStoreMacaroon, args.Resources)
   126  	if err != nil {
   127  		return nil, errors.Trace(err)
   128  	}
   129  
   130  	var result api.AddPendingResourcesResult
   131  	if err := c.FacadeCall("AddPendingResources", &apiArgs, &result); err != nil {
   132  		return nil, errors.Trace(err)
   133  	}
   134  	if result.Error != nil {
   135  		err := common.RestoreError(result.Error)
   136  		return nil, errors.Trace(err)
   137  	}
   138  
   139  	if len(result.PendingIDs) != len(args.Resources) {
   140  		return nil, errors.Errorf("bad data from server: expected %d IDs, got %d", len(args.Resources), len(result.PendingIDs))
   141  	}
   142  	for i, id := range result.PendingIDs {
   143  		if id == "" {
   144  			return nil, errors.Errorf("bad data from server: got an empty ID for resource %q", args.Resources[i].Name)
   145  		}
   146  		// TODO(ericsnow) Do other validation?
   147  	}
   148  
   149  	return result.PendingIDs, nil
   150  }
   151  
   152  // AddPendingResource sends the provided resource blob up to Juju
   153  // without making it available yet. For example, AddPendingResource()
   154  // is used before the application is deployed.
   155  func (c Client) AddPendingResource(applicationID string, res charmresource.Resource, filename string, reader io.ReadSeeker) (pendingID string, err error) {
   156  	ids, err := c.AddPendingResources(AddPendingResourcesArgs{
   157  		ApplicationID: applicationID,
   158  		Resources:     []charmresource.Resource{res},
   159  	})
   160  	if err != nil {
   161  		return "", errors.Trace(err)
   162  	}
   163  	pendingID = ids[0]
   164  
   165  	if reader != nil {
   166  		uReq, err := api.NewUploadRequest(applicationID, res.Name, filename, reader)
   167  		if err != nil {
   168  			return "", errors.Trace(err)
   169  		}
   170  		uReq.PendingID = pendingID
   171  		req, err := uReq.HTTPRequest()
   172  		if err != nil {
   173  			return "", errors.Trace(err)
   174  		}
   175  
   176  		var response api.UploadResult // ignored
   177  		if err := c.doer.Do(req, reader, &response); err != nil {
   178  			return "", errors.Trace(err)
   179  		}
   180  	}
   181  
   182  	return pendingID, nil
   183  }
   184  
   185  func resolveErrors(errs []error) error {
   186  	switch len(errs) {
   187  	case 0:
   188  		return nil
   189  	case 1:
   190  		return errs[0]
   191  	default:
   192  		msgs := make([]string, len(errs))
   193  		for i, err := range errs {
   194  			msgs[i] = err.Error()
   195  		}
   196  		return errors.New(strings.Join(msgs, "\n"))
   197  	}
   198  }