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, &params.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  }