github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"io/ioutil"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  
    14  	"github.com/juju/errors"
    15  
    16  	"github.com/juju/juju/apiserver/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 := ioutil.TempFile(tmpDir, "charm")
    37  	if err != nil {
    38  		return "", errors.Annotate(err, "cannot create charm archive file")
    39  	}
    40  	if _, err = io.Copy(charmFile, reader); err != nil {
    41  		cleanupFile(charmFile)
    42  		return "", errors.Annotate(err, "error processing charm archive download")
    43  	}
    44  	charmFile.Close()
    45  	return charmFile.Name(), nil
    46  }
    47  
    48  // On windows we cannot remove a file until it has been closed
    49  // If this poses an active problem somewhere else it will be refactored in
    50  // utils and used everywhere.
    51  func cleanupFile(file *os.File) {
    52  	// Errors are ignored because it is ok for this to be called when
    53  	// the file is already closed or has been moved.
    54  	file.Close()
    55  	os.Remove(file.Name())
    56  }
    57  
    58  // CharmArchiveEntry retrieves the specified entry from the zip archive.
    59  func CharmArchiveEntry(charmPath, entryPath string, wantIcon bool) ([]byte, error) {
    60  	// TODO(fwereade) 2014-01-27 bug #1285685
    61  	// This doesn't handle symlinks helpfully, and should be talking in
    62  	// terms of bundles rather than zip readers; but this demands thought
    63  	// and design and is not amenable to a quick fix.
    64  	zipReader, err := zip.OpenReader(charmPath)
    65  	if err != nil {
    66  		return nil, errors.Annotatef(err, "unable to read charm")
    67  	}
    68  	defer zipReader.Close()
    69  	for _, file := range zipReader.File {
    70  		if path.Clean(file.Name) != entryPath {
    71  			continue
    72  		}
    73  		fileInfo := file.FileInfo()
    74  		if fileInfo.IsDir() {
    75  			return nil, &params.Error{
    76  				Message: "directory listing not allowed",
    77  				Code:    params.CodeForbidden,
    78  			}
    79  		}
    80  		contents, err := file.Open()
    81  		if err != nil {
    82  			return nil, errors.Annotatef(err, "unable to read file %q", entryPath)
    83  		}
    84  		defer contents.Close()
    85  		return ioutil.ReadAll(contents)
    86  	}
    87  	if wantIcon {
    88  		// An icon was requested but none was found in the archive so
    89  		// return the default icon instead.
    90  		return []byte(DefaultCharmIcon), nil
    91  	}
    92  	return nil, errors.NotFoundf("charm file")
    93  }