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  }