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 }