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 }