github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/agent/uniter/resource.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "context" 8 "io" 9 "net/http" 10 "path" 11 12 "github.com/juju/errors" 13 "github.com/juju/names/v5" 14 15 "github.com/juju/juju/api/base" 16 api "github.com/juju/juju/api/client/resources" 17 apihttp "github.com/juju/juju/api/http" 18 apiservererrors "github.com/juju/juju/apiserver/errors" 19 "github.com/juju/juju/core/resources" 20 "github.com/juju/juju/rpc/params" 21 ) 22 23 // FacadeCaller exposes the raw API caller functionality needed here. 24 type FacadeCaller interface { 25 // FacadeCall makes an API request. 26 FacadeCall(request string, args, response interface{}) error 27 } 28 29 // UnitHTTPClient exposes the raw API HTTP caller functionality needed here. 30 type UnitHTTPClient interface { 31 apihttp.HTTPDoer 32 33 // Unit Returns the name of the unit for this client. 34 Unit() string 35 } 36 37 // NewResourcesFacadeClient creates a new API client for the resources 38 // portion of the uniter facade. 39 func NewResourcesFacadeClient(caller base.APICaller, unitTag names.UnitTag) (*ResourcesFacadeClient, error) { 40 facadeCaller := base.NewFacadeCaller(caller, "ResourcesHookContext") 41 httpClient, err := caller.HTTPClient() 42 if err != nil { 43 return nil, errors.Trace(err) 44 } 45 46 ctx := caller.Context() 47 return &ResourcesFacadeClient{ 48 FacadeCaller: facadeCaller, 49 HTTPDoer: NewUnitHTTPClient(ctx, httpClient, unitTag.String()), 50 ctx: ctx, 51 }, nil 52 } 53 54 // ResourcesFacadeClient is an API client for the resources portion 55 // of the uniter facade. 56 type ResourcesFacadeClient struct { 57 FacadeCaller 58 apihttp.HTTPDoer 59 ctx context.Context 60 } 61 62 // GetResource opens the resource (metadata/blob), if it exists, via 63 // the HTTP API and returns it. If it does not exist or hasn't been 64 // uploaded yet then errors.NotFound is returned. 65 func (c *ResourcesFacadeClient) GetResource(resourceName string) (resources.Resource, io.ReadCloser, error) { 66 var response *http.Response 67 req, err := api.NewHTTPDownloadRequest(resourceName) 68 if err != nil { 69 return resources.Resource{}, nil, errors.Annotate(err, "failed to build API request") 70 } 71 if err := c.Do(c.ctx, req, &response); err != nil { 72 return resources.Resource{}, nil, errors.Annotate(err, "HTTP request failed") 73 } 74 75 // HACK(katco): Combine this into one request? 76 resourceInfo, err := c.getResourceInfo(resourceName) 77 if err != nil { 78 return resources.Resource{}, nil, errors.Trace(err) 79 } 80 81 // TODO(katco): Check headers against resource info 82 // TODO(katco): Check in on all the response headers 83 return resourceInfo, response.Body, nil 84 } 85 86 func (c *ResourcesFacadeClient) getResourceInfo(resourceName string) (resources.Resource, error) { 87 var response params.UnitResourcesResult 88 89 args := params.ListUnitResourcesArgs{ 90 ResourceNames: []string{resourceName}, 91 } 92 if err := c.FacadeCall("GetResourceInfo", &args, &response); err != nil { 93 return resources.Resource{}, errors.Annotate(err, "could not get resource info") 94 } 95 if response.Error != nil { 96 err := apiservererrors.RestoreError(response.Error) 97 return resources.Resource{}, errors.Annotate(err, "request failed on server") 98 } 99 100 if len(response.Resources) != 1 { 101 return resources.Resource{}, errors.New("got bad response from API server") 102 } 103 if response.Resources[0].Error != nil { 104 err := apiservererrors.RestoreError(response.Error) 105 return resources.Resource{}, errors.Annotate(err, "request failed for resource") 106 } 107 res, err := api.API2Resource(response.Resources[0].Resource) 108 if err != nil { 109 return resources.Resource{}, errors.Annotate(err, "got bad data from API server") 110 } 111 return res, nil 112 } 113 114 type unitHTTPClient struct { 115 apihttp.HTTPDoer 116 unitName string 117 ctx context.Context 118 } 119 120 // NewUnitHTTPClient wraps an HTTP client (a la httprequest.Client) 121 // with unit information. This allows rewriting of the URL to match 122 // the relevant unit. 123 func NewUnitHTTPClient(ctx context.Context, client apihttp.HTTPDoer, unitName string) UnitHTTPClient { 124 return &unitHTTPClient{ 125 HTTPDoer: client, 126 unitName: unitName, 127 ctx: ctx, 128 } 129 } 130 131 // Unit returns the name of the unit. 132 func (uhc unitHTTPClient) Unit() string { 133 return uhc.unitName 134 } 135 136 // Do implements httprequest.Doer. 137 func (uhc *unitHTTPClient) Do(ctx context.Context, req *http.Request, response interface{}) error { 138 req.URL.Path = path.Join("/units", uhc.unitName, req.URL.Path) 139 return uhc.HTTPDoer.Do(ctx, req, response) 140 }