github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/client/charms/downloader_s3.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charms 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 11 "github.com/juju/charm/v12" 12 "github.com/juju/errors" 13 14 "github.com/juju/juju/api/base" 15 "github.com/juju/juju/downloader" 16 ) 17 18 // CharmGetter defines a way to get charms from a bucket. 19 type CharmGetter interface { 20 // GetCharm returns an io.ReadCloser for the specified object within the 21 // specified bucket. 22 GetCharm(ctx context.Context, modelUUID, charmName string) (io.ReadCloser, error) 23 } 24 25 // NewS3CharmDownloader returns a new charm downloader that wraps a s3Caller 26 // client for the provided endpoint. 27 func NewS3CharmDownloader(charmGetter CharmGetter, apiCaller base.APICaller) *downloader.Downloader { 28 dlr := &downloader.Downloader{ 29 OpenBlob: func(req downloader.Request) (io.ReadCloser, error) { 30 streamer := NewS3CharmOpener(charmGetter, apiCaller) 31 reader, err := streamer.OpenCharm(req) 32 if err != nil { 33 return nil, errors.Trace(err) 34 } 35 return reader, nil 36 }, 37 } 38 return dlr 39 } 40 41 // CharmOpener provides the OpenCharm method. 42 type S3CharmOpener interface { 43 OpenCharm(req downloader.Request) (io.ReadCloser, error) 44 } 45 46 type s3charmOpener struct { 47 ctx context.Context 48 charmGetter CharmGetter 49 apiCaller base.APICaller 50 } 51 52 func (s *s3charmOpener) OpenCharm(req downloader.Request) (io.ReadCloser, error) { 53 // Retrieve first 8 characters of the charm archive sha256 54 if len(req.ArchiveSha256) < 8 { 55 return nil, errors.NotValidf("download request with archiveSha256 length %d", len(req.ArchiveSha256)) 56 } 57 shortSha256 := req.ArchiveSha256[0:8] 58 // Retrieve charms name 59 curl, err := charm.ParseURL(req.URL.String()) 60 if err != nil { 61 return nil, errors.Annotate(err, "did not receive a valid charm URL") 62 } 63 64 // We can ignore the second return bool from ModelTag() because if it's 65 // a controller model, then it will fail later when calling GetCharm. 66 modelTag, _ := s.apiCaller.ModelTag() 67 68 charmRef := fmt.Sprintf("%s-%s", curl.Name, shortSha256) 69 return s.charmGetter.GetCharm(s.ctx, modelTag.Id(), charmRef) 70 } 71 72 // NewS3CharmOpener returns a charm opener for the specified s3Caller. 73 func NewS3CharmOpener(charmGetter CharmGetter, apiCaller base.APICaller) S3CharmOpener { 74 return &s3charmOpener{ 75 ctx: context.Background(), 76 charmGetter: charmGetter, 77 apiCaller: apiCaller, 78 } 79 }