github.com/aclaygray/packer@v1.3.2/post-processor/vagrant/util.go (about)

     1  package vagrant
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/flate"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  
    14  	"github.com/hashicorp/packer/packer"
    15  	"github.com/klauspost/pgzip"
    16  )
    17  
    18  var (
    19  	// ErrInvalidCompressionLevel is returned when the compression level passed
    20  	// to gzip is not in the expected range. See compress/flate for details.
    21  	ErrInvalidCompressionLevel = fmt.Errorf(
    22  		"Invalid compression level. Expected an integer from -1 to 9.")
    23  )
    24  
    25  // Copies a file by copying the contents of the file to another place.
    26  func CopyContents(dst, src string) error {
    27  	srcF, err := os.Open(src)
    28  	if err != nil {
    29  		return err
    30  	}
    31  	defer srcF.Close()
    32  
    33  	dstDir, _ := filepath.Split(dst)
    34  	if dstDir != "" {
    35  		err := os.MkdirAll(dstDir, 0755)
    36  		if err != nil {
    37  			return err
    38  		}
    39  	}
    40  
    41  	dstF, err := os.Create(dst)
    42  	if err != nil {
    43  		return err
    44  	}
    45  	defer dstF.Close()
    46  
    47  	if _, err := io.Copy(dstF, srcF); err != nil {
    48  		return err
    49  	}
    50  
    51  	return nil
    52  }
    53  
    54  // Creates a (hard) link to a file, ensuring that all parent directories also exist.
    55  func LinkFile(dst, src string) error {
    56  	dstDir, _ := filepath.Split(dst)
    57  	if dstDir != "" {
    58  		err := os.MkdirAll(dstDir, 0755)
    59  		if err != nil {
    60  			return err
    61  		}
    62  	}
    63  
    64  	if err := os.Link(src, dst); err != nil {
    65  		return err
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  // DirToBox takes the directory and compresses it into a Vagrant-compatible
    72  // box. This function does not perform checks to verify that dir is
    73  // actually a proper box. This is an expected precondition.
    74  func DirToBox(dst, dir string, ui packer.Ui, level int) error {
    75  	log.Printf("Turning dir into box: %s => %s", dir, dst)
    76  
    77  	// Make the containing directory, if it does not already exist
    78  	err := os.MkdirAll(filepath.Dir(dst), 0755)
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	dstF, err := os.Create(dst)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	defer dstF.Close()
    88  
    89  	var dstWriter io.WriteCloser = dstF
    90  	if level != flate.NoCompression {
    91  		log.Printf("Compressing with gzip compression level: %d", level)
    92  		gzipWriter, err := makePgzipWriter(dstWriter, level)
    93  		if err != nil {
    94  			return err
    95  		}
    96  		defer gzipWriter.Close()
    97  
    98  		dstWriter = gzipWriter
    99  	}
   100  
   101  	tarWriter := tar.NewWriter(dstWriter)
   102  	defer tarWriter.Close()
   103  
   104  	// This is the walk func that tars each of the files in the dir
   105  	tarWalk := func(path string, info os.FileInfo, prevErr error) error {
   106  		// If there was a prior error, return it
   107  		if prevErr != nil {
   108  			return prevErr
   109  		}
   110  
   111  		// Skip directories
   112  		if info.IsDir() {
   113  			log.Printf("Skipping directory '%s' for box '%s'", path, dst)
   114  			return nil
   115  		}
   116  
   117  		log.Printf("Box add: '%s' to '%s'", path, dst)
   118  		f, err := os.Open(path)
   119  		if err != nil {
   120  			return err
   121  		}
   122  		defer f.Close()
   123  
   124  		header, err := tar.FileInfoHeader(info, "")
   125  		if err != nil {
   126  			return err
   127  		}
   128  
   129  		// go >=1.10 wants to use GNU tar format to workaround issues in
   130  		// libarchive < 3.3.2
   131  		setHeaderFormat(header)
   132  
   133  		// We have to set the Name explicitly because it is supposed to
   134  		// be a relative path to the root. Otherwise, the tar ends up
   135  		// being a bunch of files in the root, even if they're actually
   136  		// nested in a dir in the original "dir" param.
   137  		header.Name, err = filepath.Rel(dir, path)
   138  		if err != nil {
   139  			return err
   140  		}
   141  
   142  		if ui != nil {
   143  			ui.Message(fmt.Sprintf("Compressing: %s", header.Name))
   144  		}
   145  
   146  		if err := tarWriter.WriteHeader(header); err != nil {
   147  			return err
   148  		}
   149  
   150  		if _, err := io.Copy(tarWriter, f); err != nil {
   151  			return err
   152  		}
   153  
   154  		return nil
   155  	}
   156  
   157  	// Tar.gz everything up
   158  	return filepath.Walk(dir, tarWalk)
   159  }
   160  
   161  // WriteMetadata writes the "metadata.json" file for a Vagrant box.
   162  func WriteMetadata(dir string, contents interface{}) error {
   163  	if _, err := os.Stat(filepath.Join(dir, "metadata.json")); os.IsNotExist(err) {
   164  		f, err := os.Create(filepath.Join(dir, "metadata.json"))
   165  		if err != nil {
   166  			return err
   167  		}
   168  		defer f.Close()
   169  
   170  		enc := json.NewEncoder(f)
   171  		return enc.Encode(contents)
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  func makePgzipWriter(output io.WriteCloser, compressionLevel int) (io.WriteCloser, error) {
   178  	gzipWriter, err := pgzip.NewWriterLevel(output, compressionLevel)
   179  	if err != nil {
   180  		return nil, ErrInvalidCompressionLevel
   181  	}
   182  	gzipWriter.SetConcurrency(500000, runtime.GOMAXPROCS(-1))
   183  	return gzipWriter, nil
   184  }