github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/charms.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "archive/zip" 8 "io" 9 "os" 10 "path" 11 "path/filepath" 12 13 "github.com/juju/errors" 14 15 corecharm "github.com/juju/juju/core/charm" 16 "github.com/juju/juju/rpc/params" 17 "github.com/juju/juju/state/storage" 18 ) 19 20 // ReadCharmFromStorage fetches the charm at the specified path from the store 21 // and copies it to a temp directory in dataDir. 22 func ReadCharmFromStorage(store storage.Storage, dataDir, storagePath string) (string, error) { 23 // Ensure the working directory exists. 24 tmpDir := filepath.Join(dataDir, "charm-get-tmp") 25 if err := os.MkdirAll(tmpDir, 0755); err != nil { 26 return "", errors.Annotate(err, "cannot create charms tmp directory") 27 } 28 29 // Use the storage to retrieve and save the charm archive. 30 reader, _, err := store.Get(storagePath) 31 if err != nil { 32 return "", errors.Annotate(err, "cannot get charm from model storage") 33 } 34 defer reader.Close() 35 36 charmFile, err := os.CreateTemp(tmpDir, "charm") 37 if err != nil { 38 return "", errors.Annotate(err, "cannot create charm archive file") 39 } 40 defer charmFile.Close() 41 42 if _, err = io.Copy(charmFile, reader); err != nil { 43 return "", errors.Annotate(err, "error processing charm archive download") 44 } 45 return charmFile.Name(), nil 46 } 47 48 // CharmArchiveEntry retrieves the specified entry from the zip archive. 49 func CharmArchiveEntry(charmPath, entryPath string, wantIcon bool) ([]byte, error) { 50 // TODO(fwereade) 2014-01-27 bug #1285685 51 // This doesn't handle symlinks helpfully, and should be talking in 52 // terms of bundles rather than zip readers; but this demands thought 53 // and design and is not amenable to a quick fix. 54 zipReader, err := zip.OpenReader(charmPath) 55 if err != nil { 56 return nil, errors.Annotatef(err, "unable to read charm") 57 } 58 defer zipReader.Close() 59 for _, file := range zipReader.File { 60 if path.Clean(file.Name) != entryPath { 61 continue 62 } 63 fileInfo := file.FileInfo() 64 if fileInfo.IsDir() { 65 return nil, ¶ms.Error{ 66 Message: "directory listing not allowed", 67 Code: params.CodeForbidden, 68 } 69 } 70 contents, err := file.Open() 71 if err != nil { 72 return nil, errors.Annotatef(err, "unable to read file %q", entryPath) 73 } 74 defer contents.Close() 75 return io.ReadAll(contents) 76 } 77 if wantIcon { 78 // An icon was requested but none was found in the archive so 79 // return the default icon instead. 80 return []byte(DefaultCharmIcon), nil 81 } 82 return nil, errors.NotFoundf("charm file") 83 } 84 85 // ValidateCharmOrigin validates the Source of the charm origin for args received 86 // in a facade. This may evolve over time to include more pieces. 87 func ValidateCharmOrigin(o *params.CharmOrigin) error { 88 switch { 89 case o == nil: 90 return errors.BadRequestf("charm origin source required") 91 case corecharm.CharmHub.Matches(o.Source): 92 // If either the charm origin ID or Hash is set before a charm is 93 // downloaded, charm download will fail for charms with a forced series. 94 // The logic (refreshConfig) in sending the correct request to charmhub 95 // will break. 96 if (o.ID != "" && o.Hash == "") || 97 (o.ID == "" && o.Hash != "") { 98 return errors.BadRequestf("programming error, both CharmOrigin ID and Hash must be set or neither. See CharmHubRepository GetDownloadURL.") 99 } 100 case corecharm.Local.Matches(o.Source): 101 default: 102 return errors.BadRequestf("%q not a valid charm origin source", o.Source) 103 } 104 return nil 105 }