github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/resource"
    13  	"gopkg.in/juju/names.v2"
    14  	"gopkg.in/macaroon.v2-unstable"
    15  
    16  	"github.com/juju/juju/apiserver/common"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/charmstore"
    19  	"github.com/juju/juju/resource"
    20  	"github.com/juju/juju/resource/api"
    21  )
    22  
    23  // TODO(ericsnow) Move FacadeCaller to a component-central package.
    24  
    25  // FacadeCaller has the api/base.FacadeCaller methods needed for the component.
    26  type FacadeCaller interface {
    27  	FacadeCall(request string, params, response interface{}) error
    28  }
    29  
    30  // Doer
    31  type Doer interface {
    32  	Do(req *http.Request, body io.ReadSeeker, resp interface{}) error
    33  }
    34  
    35  // Client is the public client for the resources API facade.
    36  type Client struct {
    37  	FacadeCaller
    38  	io.Closer
    39  	doer Doer
    40  }
    41  
    42  // NewClient returns a new Client for the given raw API caller.
    43  func NewClient(caller FacadeCaller, doer Doer, closer io.Closer) *Client {
    44  	return &Client{
    45  		FacadeCaller: caller,
    46  		Closer:       closer,
    47  		doer:         doer,
    48  	}
    49  }
    50  
    51  // ListResources calls the ListResources API server method with
    52  // the given application names.
    53  func (c Client) ListResources(applications []string) ([]resource.ApplicationResources, error) {
    54  	args, err := newListResourcesArgs(applications)
    55  	if err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  
    59  	var apiResults params.ResourcesResults
    60  	if err := c.FacadeCall("ListResources", &args, &apiResults); err != nil {
    61  		return nil, errors.Trace(err)
    62  	}
    63  
    64  	if len(apiResults.Results) != len(applications) {
    65  		// We don't bother returning the results we *did* get since
    66  		// something bad happened on the server.
    67  		return nil, errors.Errorf("got invalid data from server (expected %d results, got %d)", len(applications), len(apiResults.Results))
    68  	}
    69  
    70  	var errs []error
    71  	results := make([]resource.ApplicationResources, len(applications))
    72  	for i := range applications {
    73  		apiResult := apiResults.Results[i]
    74  
    75  		result, err := api.APIResult2ApplicationResources(apiResult)
    76  		if err != nil {
    77  			errs = append(errs, errors.Trace(err))
    78  		}
    79  		results[i] = result
    80  	}
    81  	if err := resolveErrors(errs); err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  
    85  	return results, nil
    86  }
    87  
    88  // newListResourcesArgs returns the arguments for the ListResources endpoint.
    89  func newListResourcesArgs(applications []string) (params.ListResourcesArgs, error) {
    90  	var args params.ListResourcesArgs
    91  	var errs []error
    92  	for _, application := range applications {
    93  		if !names.IsValidApplication(application) {
    94  			err := errors.Errorf("invalid application %q", application)
    95  			errs = append(errs, err)
    96  			continue
    97  		}
    98  		args.Entities = append(args.Entities, params.Entity{
    99  			Tag: names.NewApplicationTag(application).String(),
   100  		})
   101  	}
   102  	if err := resolveErrors(errs); err != nil {
   103  		return args, errors.Trace(err)
   104  	}
   105  	return args, nil
   106  }
   107  
   108  // Upload sends the provided resource blob up to Juju.
   109  func (c Client) Upload(application, name, filename string, reader io.ReadSeeker) error {
   110  	uReq, err := api.NewUploadRequest(application, name, filename, reader)
   111  	if err != nil {
   112  		return errors.Trace(err)
   113  	}
   114  	req, err := uReq.HTTPRequest()
   115  	if err != nil {
   116  		return errors.Trace(err)
   117  	}
   118  
   119  	var response params.UploadResult // ignored
   120  	if err := c.doer.Do(req, reader, &response); err != nil {
   121  		return errors.Trace(err)
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  // AddPendingResourcesArgs holds the arguments to AddPendingResources().
   128  type AddPendingResourcesArgs struct {
   129  	// ApplicationID identifies the application being deployed.
   130  	ApplicationID string
   131  
   132  	// CharmID identifies the application's charm.
   133  	CharmID charmstore.CharmID
   134  
   135  	// CharmStoreMacaroon is the macaroon to use for the charm when
   136  	// interacting with the charm store.
   137  	CharmStoreMacaroon *macaroon.Macaroon
   138  
   139  	// Resources holds the charm store info for each of the resources
   140  	// that should be added/updated on the controller.
   141  	Resources []charmresource.Resource
   142  }
   143  
   144  // AddPendingResources sends the provided resource info up to Juju
   145  // without making it available yet.
   146  func (c Client) AddPendingResources(args AddPendingResourcesArgs) (pendingIDs []string, err error) {
   147  	apiArgs, err := newAddPendingResourcesArgs(args.ApplicationID, args.CharmID, args.CharmStoreMacaroon, args.Resources)
   148  	if err != nil {
   149  		return nil, errors.Trace(err)
   150  	}
   151  
   152  	var result params.AddPendingResourcesResult
   153  	if err := c.FacadeCall("AddPendingResources", &apiArgs, &result); err != nil {
   154  		return nil, errors.Trace(err)
   155  	}
   156  	if result.Error != nil {
   157  		err := common.RestoreError(result.Error)
   158  		return nil, errors.Trace(err)
   159  	}
   160  
   161  	if len(result.PendingIDs) != len(args.Resources) {
   162  		return nil, errors.Errorf("bad data from server: expected %d IDs, got %d", len(args.Resources), len(result.PendingIDs))
   163  	}
   164  	for i, id := range result.PendingIDs {
   165  		if id == "" {
   166  			return nil, errors.Errorf("bad data from server: got an empty ID for resource %q", args.Resources[i].Name)
   167  		}
   168  		// TODO(ericsnow) Do other validation?
   169  	}
   170  
   171  	return result.PendingIDs, nil
   172  }
   173  
   174  // newAddPendingResourcesArgs returns the arguments for the
   175  // AddPendingResources API endpoint.
   176  func newAddPendingResourcesArgs(applicationID string, chID charmstore.CharmID, csMac *macaroon.Macaroon, resources []charmresource.Resource) (params.AddPendingResourcesArgs, error) {
   177  	var args params.AddPendingResourcesArgs
   178  
   179  	if !names.IsValidApplication(applicationID) {
   180  		return args, errors.Errorf("invalid application %q", applicationID)
   181  	}
   182  	tag := names.NewApplicationTag(applicationID).String()
   183  
   184  	var apiResources []params.CharmResource
   185  	for _, res := range resources {
   186  		if err := res.Validate(); err != nil {
   187  			return args, errors.Trace(err)
   188  		}
   189  		apiRes := api.CharmResource2API(res)
   190  		apiResources = append(apiResources, apiRes)
   191  	}
   192  	args.Tag = tag
   193  	args.Resources = apiResources
   194  	if chID.URL != nil {
   195  		args.URL = chID.URL.String()
   196  		args.Channel = string(chID.Channel)
   197  		args.CharmStoreMacaroon = csMac
   198  	}
   199  	return args, nil
   200  }
   201  
   202  // UploadPendingResource sends the provided resource blob up to Juju
   203  // and makes it available.
   204  func (c Client) UploadPendingResource(applicationID string, res charmresource.Resource, filename string, reader io.ReadSeeker) (pendingID string, err error) {
   205  	ids, err := c.AddPendingResources(AddPendingResourcesArgs{
   206  		ApplicationID: applicationID,
   207  		Resources:     []charmresource.Resource{res},
   208  	})
   209  	if err != nil {
   210  		return "", errors.Trace(err)
   211  	}
   212  	pendingID = ids[0]
   213  
   214  	if reader != nil {
   215  		uReq, err := api.NewUploadRequest(applicationID, res.Name, filename, reader)
   216  		if err != nil {
   217  			return "", errors.Trace(err)
   218  		}
   219  		uReq.PendingID = pendingID
   220  		req, err := uReq.HTTPRequest()
   221  		if err != nil {
   222  			return "", errors.Trace(err)
   223  		}
   224  
   225  		var response params.UploadResult // ignored
   226  		if err := c.doer.Do(req, reader, &response); err != nil {
   227  			return "", errors.Trace(err)
   228  		}
   229  	}
   230  
   231  	return pendingID, nil
   232  }
   233  
   234  func resolveErrors(errs []error) error {
   235  	switch len(errs) {
   236  	case 0:
   237  		return nil
   238  	case 1:
   239  		return errs[0]
   240  	default:
   241  		msgs := make([]string, len(errs))
   242  		for i, err := range errs {
   243  			msgs[i] = err.Error()
   244  		}
   245  		return errors.New(strings.Join(msgs, "\n"))
   246  	}
   247  }