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 }