github.com/jghiloni/cli@v6.28.1-0.20170628223758-0ce05fe032a2+incompatible/actor/v2action/resource.go (about)

     1  package v2action
     2  
     3  import (
     4  	"archive/zip"
     5  	"crypto/sha1"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2"
    14  	"code.cloudfoundry.org/ykk"
    15  	log "github.com/sirupsen/logrus"
    16  )
    17  
    18  const (
    19  	DefaultFolderPermissions      = 0755
    20  	DefaultArchiveFilePermissions = 0744
    21  )
    22  
    23  type FileChangedError struct {
    24  	Filename string
    25  }
    26  
    27  func (e FileChangedError) Error() string {
    28  	return fmt.Sprint("SHA1 mismatch for:", e.Filename)
    29  }
    30  
    31  type EmptyDirectoryError struct {
    32  	Path string
    33  }
    34  
    35  func (e EmptyDirectoryError) Error() string {
    36  	return fmt.Sprint(e.Path, "is empty")
    37  }
    38  
    39  type Resource ccv2.Resource
    40  
    41  // GatherArchiveResources returns a list of resources for a directory.
    42  func (actor Actor) GatherArchiveResources(archivePath string) ([]Resource, error) {
    43  	var resources []Resource
    44  
    45  	archive, err := os.Open(archivePath)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	defer archive.Close()
    50  
    51  	reader, err := actor.newArchiveReader(archive)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	for _, archivedFile := range reader.File {
    57  		resource := Resource{Filename: filepath.ToSlash(archivedFile.Name)}
    58  		if archivedFile.FileInfo().IsDir() {
    59  			resource.Mode = DefaultFolderPermissions
    60  		} else {
    61  			fileReader, err := archivedFile.Open()
    62  			if err != nil {
    63  				return nil, err
    64  			}
    65  			defer fileReader.Close()
    66  
    67  			hash := sha1.New()
    68  
    69  			_, err = io.Copy(hash, fileReader)
    70  			if err != nil {
    71  				return nil, err
    72  			}
    73  
    74  			resource.Mode = DefaultArchiveFilePermissions
    75  			resource.SHA1 = fmt.Sprintf("%x", hash.Sum(nil))
    76  			resource.Size = archivedFile.FileInfo().Size()
    77  		}
    78  		resources = append(resources, resource)
    79  	}
    80  	return resources, nil
    81  }
    82  
    83  // GatherDirectoryResources returns a list of resources for a directory.
    84  func (_ Actor) GatherDirectoryResources(sourceDir string) ([]Resource, error) {
    85  	var resources []Resource
    86  	walkErr := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
    87  		if err != nil {
    88  			return err
    89  		}
    90  
    91  		relPath, err := filepath.Rel(sourceDir, path)
    92  		if err != nil {
    93  			return err
    94  		}
    95  
    96  		if relPath == "." {
    97  			return nil
    98  		}
    99  
   100  		resource := Resource{
   101  			Filename: filepath.ToSlash(relPath),
   102  		}
   103  
   104  		if info.IsDir() {
   105  			resource.Mode = DefaultFolderPermissions
   106  		} else {
   107  			file, err := os.Open(path)
   108  			if err != nil {
   109  				return err
   110  			}
   111  			defer file.Close()
   112  
   113  			sum := sha1.New()
   114  			_, err = io.Copy(sum, file)
   115  			if err != nil {
   116  				return err
   117  			}
   118  
   119  			resource.Mode = fixMode(info.Mode())
   120  			resource.SHA1 = fmt.Sprintf("%x", sum.Sum(nil))
   121  			resource.Size = info.Size()
   122  		}
   123  		resources = append(resources, resource)
   124  		return nil
   125  	})
   126  
   127  	if len(resources) == 0 {
   128  		return nil, EmptyDirectoryError{Path: sourceDir}
   129  	}
   130  
   131  	return resources, walkErr
   132  }
   133  
   134  // ZipArchiveResources zips an archive and a sorted (based on full
   135  // path/filename) list of resources and returns the location. On Windows, the
   136  // filemode for user is forced to be readable and executable.
   137  func (actor Actor) ZipArchiveResources(sourceArchivePath string, filesToInclude []Resource) (string, error) {
   138  	log.WithField("sourceArchive", sourceArchivePath).Info("zipping source files from archive")
   139  	zipFile, err := ioutil.TempFile("", "cf-cli-")
   140  	if err != nil {
   141  		return "", err
   142  	}
   143  	defer zipFile.Close()
   144  
   145  	writer := zip.NewWriter(zipFile)
   146  	defer writer.Close()
   147  
   148  	source, err := os.Open(sourceArchivePath)
   149  	if err != nil {
   150  		return "", err
   151  	}
   152  	defer source.Close()
   153  
   154  	reader, err := actor.newArchiveReader(source)
   155  	if err != nil {
   156  		return "", err
   157  	}
   158  
   159  	for _, archiveFile := range reader.File {
   160  		log.WithField("archiveFileName", archiveFile.Name).Debug("zipping file")
   161  
   162  		resource := actor.findInResources(archiveFile.Name, filesToInclude)
   163  		reader, openErr := archiveFile.Open()
   164  		if openErr != nil {
   165  			log.WithField("archiveFile", archiveFile.Name).Errorln("opening path in dir:", openErr)
   166  			return "", openErr
   167  		}
   168  
   169  		err = actor.addFileToZipFromFileSystem(
   170  			resource.Filename, reader, archiveFile.FileInfo(),
   171  			resource.Filename, resource.SHA1, resource.Mode, writer,
   172  		)
   173  		if err != nil {
   174  			log.WithField("archiveFileName", archiveFile.Name).Errorln("zipping file:", err)
   175  			return "", err
   176  		}
   177  	}
   178  
   179  	log.WithFields(log.Fields{
   180  		"zip_file_location": zipFile.Name(),
   181  		"zipped_file_count": len(filesToInclude),
   182  	}).Info("zip file created")
   183  	return zipFile.Name(), nil
   184  }
   185  
   186  // ZipDirectoryResources zips a directory and a sorted (based on full
   187  // path/filename) list of resources and returns the location. On Windows, the
   188  // filemode for user is forced to be readable and executable.
   189  func (actor Actor) ZipDirectoryResources(sourceDir string, filesToInclude []Resource) (string, error) {
   190  	log.WithField("sourceDir", sourceDir).Info("zipping source files from directory")
   191  	zipFile, err := ioutil.TempFile("", "cf-cli-")
   192  	if err != nil {
   193  		return "", err
   194  	}
   195  	defer zipFile.Close()
   196  
   197  	writer := zip.NewWriter(zipFile)
   198  	defer writer.Close()
   199  
   200  	for _, resource := range filesToInclude {
   201  		fullPath := filepath.Join(sourceDir, resource.Filename)
   202  		log.WithField("fullPath", fullPath).Debug("zipping file")
   203  
   204  		srcFile, err := os.Open(fullPath)
   205  		if err != nil {
   206  			log.WithField("fullPath", fullPath).Errorln("opening path in dir:", err)
   207  			return "", err
   208  		}
   209  
   210  		fileInfo, err := srcFile.Stat()
   211  		if err != nil {
   212  			log.WithField("fullPath", fullPath).Errorln("stat error in dir:", err)
   213  			return "", err
   214  		}
   215  
   216  		err = actor.addFileToZipFromFileSystem(
   217  			fullPath, srcFile, fileInfo,
   218  			resource.Filename, resource.SHA1, resource.Mode, writer,
   219  		)
   220  		if err != nil {
   221  			log.WithField("fullPath", fullPath).Errorln("zipping file:", err)
   222  			return "", err
   223  		}
   224  	}
   225  
   226  	log.WithFields(log.Fields{
   227  		"zip_file_location": zipFile.Name(),
   228  		"zipped_file_count": len(filesToInclude),
   229  	}).Info("zip file created")
   230  	return zipFile.Name(), nil
   231  }
   232  
   233  func (_ Actor) actorToCCResources(resources []Resource) []ccv2.Resource {
   234  	apiResources := make([]ccv2.Resource, 0, len(resources)) // Explicitly done to prevent nils
   235  
   236  	for _, resource := range resources {
   237  		apiResources = append(apiResources, ccv2.Resource(resource))
   238  	}
   239  
   240  	return apiResources
   241  }
   242  
   243  func (_ Actor) addFileToZipFromFileSystem(
   244  	srcPath string, srcFile io.ReadCloser, fileInfo os.FileInfo,
   245  	destPath string, sha1Sum string, mode os.FileMode, zipFile *zip.Writer,
   246  ) error {
   247  	defer srcFile.Close()
   248  
   249  	header, err := zip.FileInfoHeader(fileInfo)
   250  	if err != nil {
   251  		log.WithField("srcPath", srcPath).Errorln("getting file info in dir:", err)
   252  		return err
   253  	}
   254  
   255  	// An extra '/' indicates that this file is a directory
   256  	if fileInfo.IsDir() && !strings.HasSuffix(destPath, "/") {
   257  		destPath += "/"
   258  	}
   259  
   260  	header.Name = destPath
   261  	header.Method = zip.Deflate
   262  
   263  	header.SetMode(mode)
   264  	log.WithFields(log.Fields{
   265  		"srcPath":  srcPath,
   266  		"destPath": destPath,
   267  		"mode":     mode,
   268  	}).Debug("setting mode for file")
   269  
   270  	destFileWriter, err := zipFile.CreateHeader(header)
   271  	if err != nil {
   272  		log.Errorln("creating header:", err)
   273  		return err
   274  	}
   275  
   276  	if !fileInfo.IsDir() {
   277  		sum := sha1.New()
   278  
   279  		multi := io.MultiWriter(sum, destFileWriter)
   280  		if _, err := io.Copy(multi, srcFile); err != nil {
   281  			log.WithField("srcPath", srcPath).Errorln("copying data in dir:", err)
   282  			return err
   283  		}
   284  
   285  		if currentSum := fmt.Sprintf("%x", sum.Sum(nil)); sha1Sum != currentSum {
   286  			log.WithFields(log.Fields{
   287  				"expected":   sha1Sum,
   288  				"currentSum": currentSum,
   289  			}).Error("setting mode for file")
   290  			return FileChangedError{Filename: srcPath}
   291  		}
   292  	}
   293  
   294  	return nil
   295  }
   296  
   297  func (_ Actor) findInResources(path string, filesToInclude []Resource) Resource {
   298  	for _, resource := range filesToInclude {
   299  		if resource.Filename == filepath.ToSlash(path) {
   300  			log.WithField("resource", resource.Filename).Debug("found resource in files to include")
   301  			return resource
   302  		}
   303  	}
   304  
   305  	log.WithField("path", path).Debug("did not find resource in files to include")
   306  	return Resource{}
   307  }
   308  
   309  func (_ Actor) newArchiveReader(archive *os.File) (*zip.Reader, error) {
   310  	info, err := archive.Stat()
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	return ykk.NewReader(archive, info.Size())
   316  }