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  }