github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/client/charms/client.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package charms
     5  
     6  import (
     7  	"github.com/juju/charm/v12"
     8  	charmresource "github.com/juju/charm/v12/resource"
     9  	"github.com/juju/collections/transform"
    10  	"github.com/juju/errors"
    11  
    12  	"github.com/juju/juju/api/base"
    13  	api "github.com/juju/juju/api/client/resources"
    14  	apicharm "github.com/juju/juju/api/common/charm"
    15  	commoncharms "github.com/juju/juju/api/common/charms"
    16  	apiservererrors "github.com/juju/juju/apiserver/errors"
    17  	corebase "github.com/juju/juju/core/base"
    18  	"github.com/juju/juju/rpc/params"
    19  )
    20  
    21  // Client allows access to the charms API endpoint.
    22  type Client struct {
    23  	base.ClientFacade
    24  	*commoncharms.CharmInfoClient
    25  	facade base.FacadeCaller
    26  }
    27  
    28  // NewClient creates a new client for accessing the charms API.
    29  func NewClient(st base.APICallCloser) *Client {
    30  	frontend, backend := base.NewClientFacade(st, "Charms")
    31  	commonClient := commoncharms.NewCharmInfoClient(backend)
    32  	return &Client{ClientFacade: frontend, CharmInfoClient: commonClient, facade: backend}
    33  }
    34  
    35  // CharmToResolve holds the charm url and it's channel to be resolved.
    36  type CharmToResolve struct {
    37  	URL         *charm.URL
    38  	Origin      apicharm.Origin
    39  	SwitchCharm bool
    40  }
    41  
    42  // ResolvedCharm holds resolved charm data.
    43  type ResolvedCharm struct {
    44  	URL            *charm.URL
    45  	Origin         apicharm.Origin
    46  	SupportedBases []corebase.Base
    47  	Error          error
    48  }
    49  
    50  // ResolveCharms resolves the given charm URLs with an optionally specified
    51  // preferred channel.
    52  func (c *Client) ResolveCharms(charms []CharmToResolve) ([]ResolvedCharm, error) {
    53  	args := params.ResolveCharmsWithChannel{
    54  		Resolve: make([]params.ResolveCharmWithChannel, len(charms)),
    55  	}
    56  	for i, ch := range charms {
    57  		args.Resolve[i] = params.ResolveCharmWithChannel{
    58  			Reference:   ch.URL.String(),
    59  			Origin:      ch.Origin.ParamsCharmOrigin(),
    60  			SwitchCharm: ch.SwitchCharm,
    61  		}
    62  	}
    63  	if c.BestAPIVersion() < 7 {
    64  		var result params.ResolveCharmWithChannelResultsV6
    65  		if err := c.facade.FacadeCall("ResolveCharms", args, &result); err != nil {
    66  			return nil, errors.Trace(apiservererrors.RestoreError(err))
    67  		}
    68  		return transform.Slice(result.Results, c.resolveCharmV6), nil
    69  	}
    70  
    71  	var result params.ResolveCharmWithChannelResults
    72  	if err := c.facade.FacadeCall("ResolveCharms", args, &result); err != nil {
    73  		return nil, errors.Trace(apiservererrors.RestoreError(err))
    74  	}
    75  	return transform.Slice(result.Results, c.resolveCharm), nil
    76  }
    77  
    78  func (c *Client) resolveCharm(r params.ResolveCharmWithChannelResult) ResolvedCharm {
    79  	if r.Error != nil {
    80  		return ResolvedCharm{Error: apiservererrors.RestoreError(r.Error)}
    81  	}
    82  	curl, err := charm.ParseURL(r.URL)
    83  	if err != nil {
    84  		return ResolvedCharm{Error: apiservererrors.RestoreError(err)}
    85  	}
    86  	origin, err := apicharm.APICharmOrigin(r.Origin)
    87  	if err != nil {
    88  		return ResolvedCharm{Error: apiservererrors.RestoreError(err)}
    89  	}
    90  
    91  	supportedBases, err := transform.SliceOrErr(r.SupportedBases, func(in params.Base) (corebase.Base, error) {
    92  		return corebase.ParseBase(in.Name, in.Channel)
    93  	})
    94  	if err != nil {
    95  		return ResolvedCharm{Error: apiservererrors.RestoreError(err)}
    96  	}
    97  	return ResolvedCharm{
    98  		URL:            curl,
    99  		Origin:         origin,
   100  		SupportedBases: supportedBases,
   101  	}
   102  }
   103  
   104  func (c *Client) resolveCharmV6(r params.ResolveCharmWithChannelResultV6) ResolvedCharm {
   105  	if r.Error != nil {
   106  		return ResolvedCharm{Error: apiservererrors.RestoreError(r.Error)}
   107  	}
   108  	curl, err := charm.ParseURL(r.URL)
   109  	if err != nil {
   110  		return ResolvedCharm{Error: apiservererrors.RestoreError(err)}
   111  	}
   112  	origin, err := apicharm.APICharmOrigin(r.Origin)
   113  	if err != nil {
   114  		return ResolvedCharm{Error: apiservererrors.RestoreError(err)}
   115  	}
   116  	supportedBases, err := transform.SliceOrErr(r.SupportedSeries, corebase.GetBaseFromSeries)
   117  	if err != nil {
   118  		return ResolvedCharm{Error: apiservererrors.RestoreError(err)}
   119  	}
   120  	return ResolvedCharm{
   121  		URL:            curl,
   122  		Origin:         origin,
   123  		SupportedBases: supportedBases,
   124  	}
   125  }
   126  
   127  // DownloadInfo holds the URL and Origin for a charm that requires downloading
   128  // on the client side. This is mainly for bundles as we don't resolve bundles
   129  // on the server.
   130  type DownloadInfo struct {
   131  	URL    string
   132  	Origin apicharm.Origin
   133  }
   134  
   135  // GetDownloadInfo will get a download information from the given charm URL
   136  // using the appropriate charm store.
   137  func (c *Client) GetDownloadInfo(curl *charm.URL, origin apicharm.Origin) (DownloadInfo, error) {
   138  	args := params.CharmURLAndOrigins{
   139  		Entities: []params.CharmURLAndOrigin{{
   140  			CharmURL: curl.String(),
   141  			Origin:   origin.ParamsCharmOrigin(),
   142  		}},
   143  	}
   144  	var results params.DownloadInfoResults
   145  	if err := c.facade.FacadeCall("GetDownloadInfos", args, &results); err != nil {
   146  		return DownloadInfo{}, errors.Trace(err)
   147  	}
   148  	if num := len(results.Results); num != 1 {
   149  		return DownloadInfo{}, errors.Errorf("expected one result, received %d", num)
   150  	}
   151  	result := results.Results[0]
   152  	origin, err := apicharm.APICharmOrigin(result.Origin)
   153  	if err != nil {
   154  		return DownloadInfo{}, errors.Trace(err)
   155  	}
   156  	return DownloadInfo{
   157  		URL:    result.URL,
   158  		Origin: origin,
   159  	}, nil
   160  }
   161  
   162  // AddCharm adds the given charm URL (which must include revision) to
   163  // the model, if it does not exist yet. Local charms are not
   164  // supported, only charm store and charm hub URLs. See also AddLocalCharm().
   165  //
   166  // If the AddCharm API call fails because of an authorization error
   167  // when retrieving the charm from the charm store, an error
   168  // satisfying params.IsCodeUnauthorized will be returned.
   169  func (c *Client) AddCharm(curl *charm.URL, origin apicharm.Origin, force bool) (apicharm.Origin, error) {
   170  	args := params.AddCharmWithOrigin{
   171  		URL:    curl.String(),
   172  		Origin: origin.ParamsCharmOrigin(),
   173  		Force:  force,
   174  	}
   175  	var result params.CharmOriginResult
   176  	if err := c.facade.FacadeCall("AddCharm", args, &result); err != nil {
   177  		return apicharm.Origin{}, errors.Trace(err)
   178  	}
   179  	return apicharm.APICharmOrigin(result.Origin)
   180  }
   181  
   182  // CheckCharmPlacement checks to see if a charm can be placed into the
   183  // application. If the application doesn't exist then it is considered fine to
   184  // be placed there.
   185  func (c *Client) CheckCharmPlacement(applicationName string, curl *charm.URL) error {
   186  	args := params.ApplicationCharmPlacements{
   187  		Placements: []params.ApplicationCharmPlacement{{
   188  			Application: applicationName,
   189  			CharmURL:    curl.String(),
   190  		}},
   191  	}
   192  	var result params.ErrorResults
   193  	if err := c.facade.FacadeCall("CheckCharmPlacement", args, &result); err != nil {
   194  		return errors.Trace(err)
   195  	}
   196  	return result.OneError()
   197  }
   198  
   199  // ListCharmResources returns a list of associated resources for a given charm.
   200  func (c *Client) ListCharmResources(curl string, origin apicharm.Origin) ([]charmresource.Resource, error) {
   201  	args := params.CharmURLAndOrigins{
   202  		Entities: []params.CharmURLAndOrigin{{
   203  			CharmURL: curl,
   204  			Origin:   origin.ParamsCharmOrigin(),
   205  		}},
   206  	}
   207  	var results params.CharmResourcesResults
   208  	if err := c.facade.FacadeCall("ListCharmResources", args, &results); err != nil {
   209  		return nil, errors.Trace(err)
   210  	}
   211  
   212  	if n := len(results.Results); n != 1 {
   213  		return nil, errors.Errorf("expected 1 result, received %d", n)
   214  	}
   215  
   216  	result := results.Results[0]
   217  	resources := make([]charmresource.Resource, len(result))
   218  	for i, res := range result {
   219  		if res.Error != nil {
   220  			return nil, errors.Trace(res.Error)
   221  		}
   222  
   223  		chRes, err := api.API2CharmResource(res.CharmResource)
   224  		if err != nil {
   225  			return nil, errors.Annotate(err, "unexpected charm resource")
   226  		}
   227  		resources[i] = chRes
   228  	}
   229  
   230  	return resources, nil
   231  }