github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/charmrevisionupdater/charmhub.go (about) 1 // Copyright 2020 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // TODO(benhoyt) - also add caching and retries 5 6 package charmrevisionupdater 7 8 import ( 9 "context" 10 "time" 11 12 "github.com/juju/charm/v12/resource" 13 "github.com/juju/errors" 14 15 "github.com/juju/juju/charmhub" 16 "github.com/juju/juju/charmhub/transport" 17 "github.com/juju/juju/core/charm/metrics" 18 ) 19 20 // charmhubID holds identifying information for several charms for a 21 // charmhubLatestCharmInfo call. 22 type charmhubID struct { 23 id string 24 revision int 25 channel string 26 osType string 27 osChannel string 28 arch string 29 metrics map[metrics.MetricKey]string 30 // Required for charmhub only. instanceKey is a unique string associated 31 // with the application. To assist with keeping KPI data in charmhub, it 32 // must be the same for every charmhub Refresh action related to an 33 // application. Create with the charmhub.CreateInstanceKey method. 34 // LP: 1944582 35 instanceKey string 36 } 37 38 // charmhubResult is the type charmhubLatestCharmInfo returns: information 39 // about a charm revision and its resources. 40 type charmhubResult struct { 41 name string 42 timestamp time.Time 43 revision int 44 resources []resource.Resource 45 error error 46 } 47 48 // CharmhubRefreshClient is an interface for the methods of the charmhub 49 // client that we need. 50 type CharmhubRefreshClient interface { 51 RefreshWithRequestMetrics(ctx context.Context, config charmhub.RefreshConfig, metrics map[metrics.MetricKey]map[metrics.MetricKey]string) ([]transport.RefreshResponse, error) 52 RefreshWithMetricsOnly(ctx context.Context, metrics map[metrics.MetricKey]map[metrics.MetricKey]string) error 53 } 54 55 // charmhubLatestCharmInfo fetches the latest information about the given 56 // charms from charmhub's "charm_refresh" API. 57 func charmhubLatestCharmInfo(client CharmhubRefreshClient, metrics map[metrics.MetricKey]map[metrics.MetricKey]string, ids []charmhubID, now time.Time) ([]charmhubResult, error) { 58 cfgs := make([]charmhub.RefreshConfig, len(ids)) 59 for i, id := range ids { 60 base := charmhub.RefreshBase{ 61 Architecture: id.arch, 62 Name: id.osType, 63 Channel: id.osChannel, 64 } 65 cfg, err := charmhub.RefreshOne(id.instanceKey, id.id, id.revision, id.channel, base) 66 if err != nil { 67 return nil, errors.Trace(err) 68 } 69 cfg, err = charmhub.AddConfigMetrics(cfg, id.metrics) 70 if err != nil { 71 return nil, errors.Trace(err) 72 } 73 cfgs[i] = cfg 74 } 75 config := charmhub.RefreshMany(cfgs...) 76 77 ctx, cancel := context.WithTimeout(context.TODO(), charmhub.RefreshTimeout) 78 defer cancel() 79 responses, err := client.RefreshWithRequestMetrics(ctx, config, metrics) 80 if err != nil { 81 return nil, errors.Trace(err) 82 } 83 84 results := make([]charmhubResult, len(responses)) 85 for i, response := range responses { 86 results[i] = refreshResponseToCharmhubResult(response, now) 87 } 88 return results, nil 89 } 90 91 // refreshResponseToCharmhubResult converts a raw RefreshResponse from the 92 // charmhub API into a charmhubResult. 93 func refreshResponseToCharmhubResult(response transport.RefreshResponse, now time.Time) charmhubResult { 94 if response.Error != nil { 95 return charmhubResult{ 96 error: errors.Errorf("charmhub API error %s: %s", response.Error.Code, response.Error.Message), 97 } 98 } 99 var resources []resource.Resource 100 for _, r := range response.Entity.Resources { 101 fingerprint, err := resource.ParseFingerprint(r.Download.HashSHA384) 102 if err != nil { 103 logger.Warningf("invalid resource fingerprint %q: %v", r.Download.HashSHA384, err) 104 continue 105 } 106 typ, err := resource.ParseType(r.Type) 107 if err != nil { 108 logger.Warningf("invalid resource type %q: %v", r.Type, err) 109 continue 110 } 111 res := resource.Resource{ 112 Meta: resource.Meta{ 113 Name: r.Name, 114 Type: typ, 115 Path: r.Filename, 116 Description: r.Description, 117 }, 118 Origin: resource.OriginStore, 119 Revision: r.Revision, 120 Fingerprint: fingerprint, 121 Size: int64(r.Download.Size), 122 } 123 resources = append(resources, res) 124 } 125 return charmhubResult{ 126 name: response.Name, 127 timestamp: now, 128 revision: response.Entity.Revision, 129 resources: resources, 130 } 131 }