github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/charms/services/storage.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package services
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/utils/v3"
    12  
    13  	charmdownloader "github.com/juju/juju/core/charm/downloader"
    14  	"github.com/juju/juju/state"
    15  	stateerrors "github.com/juju/juju/state/errors"
    16  )
    17  
    18  // CharmStorageConfig encapsulates the information required for creating a
    19  // new CharmStorage instance.
    20  type CharmStorageConfig struct {
    21  	// The logger to use.
    22  	Logger loggo.Logger
    23  
    24  	// A factory for accessing model-scoped storage for charm blobs.
    25  	StorageFactory func(modelUUID string) Storage
    26  
    27  	StateBackend StateBackend
    28  }
    29  
    30  // CharmStorage provides an abstraction for storing charm blobs.
    31  type CharmStorage struct {
    32  	logger         loggo.Logger
    33  	stateBackend   StateBackend
    34  	storageFactory func(modelUUID string) Storage
    35  	uuidGenerator  func() (utils.UUID, error)
    36  }
    37  
    38  // NewCharmStorage creates a new CharmStorage instance with the specified config.
    39  func NewCharmStorage(cfg CharmStorageConfig) *CharmStorage {
    40  	return &CharmStorage{
    41  		logger:         cfg.Logger.Child("charmstorage"),
    42  		stateBackend:   cfg.StateBackend,
    43  		storageFactory: cfg.StorageFactory,
    44  		uuidGenerator:  utils.NewUUID,
    45  	}
    46  }
    47  
    48  // PrepareToStoreCharm ensures that the store is ready to process the specified
    49  // charm URL. If the blob for the charm is already stored, the method returns
    50  // an error to indicate this.
    51  func (s *CharmStorage) PrepareToStoreCharm(charmURL string) error {
    52  	ch, err := s.stateBackend.PrepareCharmUpload(charmURL)
    53  	if err != nil {
    54  		return errors.Trace(err)
    55  	}
    56  
    57  	if ch.IsUploaded() {
    58  		return charmdownloader.NewCharmAlreadyStoredError(charmURL)
    59  	}
    60  
    61  	return nil
    62  }
    63  
    64  // CharmStorage attempts to store the contents of a downloaded charm.
    65  func (s *CharmStorage) Store(charmURL string, downloadedCharm charmdownloader.DownloadedCharm) error {
    66  	s.logger.Tracef("store %q", charmURL)
    67  	storage := s.storageFactory(s.stateBackend.ModelUUID())
    68  	storagePath, err := s.charmArchiveStoragePath(charmURL)
    69  	if err != nil {
    70  		return errors.Annotate(err, "cannot generate charm archive name")
    71  	}
    72  	if err := storage.Put(storagePath, downloadedCharm.CharmData, downloadedCharm.Size); err != nil {
    73  		return errors.Annotate(err, "cannot add charm to storage")
    74  	}
    75  
    76  	info := state.CharmInfo{
    77  		StoragePath: storagePath,
    78  		Charm:       downloadedCharm.Charm,
    79  		ID:          charmURL,
    80  		SHA256:      downloadedCharm.SHA256,
    81  		Version:     downloadedCharm.CharmVersion,
    82  	}
    83  
    84  	// Now update the charm data in state and mark it as no longer pending.
    85  	_, err = s.stateBackend.UpdateUploadedCharm(info)
    86  	if err != nil {
    87  		alreadyUploaded := err == stateerrors.ErrCharmRevisionAlreadyModified ||
    88  			errors.Cause(err) == stateerrors.ErrCharmRevisionAlreadyModified ||
    89  			stateerrors.IsCharmAlreadyUploadedError(err)
    90  		if err := storage.Remove(storagePath); err != nil {
    91  			if alreadyUploaded {
    92  				s.logger.Errorf("cannot remove duplicated charm archive from storage: %v", err)
    93  			} else {
    94  				s.logger.Errorf("cannot remove unsuccessfully recorded charm archive from storage: %v", err)
    95  			}
    96  		}
    97  		if alreadyUploaded {
    98  			// Somebody else managed to upload and update the charm in
    99  			// state before us. This is not an error.
   100  			return nil
   101  		}
   102  		return errors.Trace(err)
   103  	}
   104  	return nil
   105  }
   106  
   107  // charmArchiveStoragePath returns a string that is suitable as a
   108  // storage path, using a random UUID to avoid colliding with concurrent
   109  // uploads.
   110  func (s *CharmStorage) charmArchiveStoragePath(charmURL string) (string, error) {
   111  	uuid, err := s.uuidGenerator()
   112  	if err != nil {
   113  		return "", err
   114  	}
   115  	return fmt.Sprintf("charms/%s-%s", charmURL, uuid), nil
   116  }