2014-04-26 12:22:10 -04:00
|
|
|
package compress
|
|
|
|
|
|
|
|
import (
|
2015-06-10 17:04:24 -04:00
|
|
|
"archive/tar"
|
2015-06-16 22:08:22 -04:00
|
|
|
"archive/zip"
|
2015-06-10 17:04:24 -04:00
|
|
|
"compress/gzip"
|
2014-04-26 12:22:10 -04:00
|
|
|
"fmt"
|
2014-06-12 16:45:37 -04:00
|
|
|
"io"
|
|
|
|
"os"
|
2015-06-16 22:08:22 -04:00
|
|
|
"path/filepath"
|
2015-06-18 03:47:33 -04:00
|
|
|
"regexp"
|
2015-06-16 22:08:22 -04:00
|
|
|
"runtime"
|
2014-09-08 13:28:16 -04:00
|
|
|
|
2015-06-16 22:08:22 -04:00
|
|
|
"github.com/klauspost/pgzip"
|
2014-09-08 13:28:16 -04:00
|
|
|
"github.com/mitchellh/packer/common"
|
2015-06-10 16:33:50 -04:00
|
|
|
"github.com/mitchellh/packer/helper/config"
|
2014-09-08 13:28:16 -04:00
|
|
|
"github.com/mitchellh/packer/packer"
|
2015-06-10 16:33:50 -04:00
|
|
|
"github.com/mitchellh/packer/template/interpolate"
|
2015-06-16 22:08:22 -04:00
|
|
|
"github.com/pierrec/lz4"
|
2014-04-26 12:22:10 -04:00
|
|
|
)
|
|
|
|
|
2015-06-16 22:08:22 -04:00
|
|
|
type Config struct {
|
|
|
|
common.PackerConfig `mapstructure:",squash"`
|
|
|
|
OutputPath string `mapstructure:"output"`
|
2015-06-18 03:47:33 -04:00
|
|
|
Level int `mapstructure:"level"`
|
2015-06-16 22:08:22 -04:00
|
|
|
KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
|
2015-06-18 03:47:33 -04:00
|
|
|
Archive string
|
|
|
|
Algorithm string
|
2015-06-16 22:08:22 -04:00
|
|
|
ctx *interpolate.Context
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
|
|
|
|
2015-06-10 16:46:21 -04:00
|
|
|
type PostProcessor struct {
|
2015-06-12 20:25:09 -04:00
|
|
|
config Config
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
// ErrInvalidCompressionLevel is returned when the compression level passed to
|
|
|
|
// gzip is not in the expected range. See compress/flate for details.
|
|
|
|
var ErrInvalidCompressionLevel = fmt.Errorf(
|
|
|
|
"Invalid compression level. Expected an integer from -1 to 9.")
|
|
|
|
|
|
|
|
var ErrWrongInputCount = fmt.Errorf(
|
|
|
|
"Can only have 1 input file when not using tar/zip")
|
|
|
|
|
|
|
|
func detectFromFilename(config *Config) error {
|
|
|
|
re := regexp.MustCompile("^.+?(?:\\.([a-z0-9]+))?\\.([a-z0-9]+)$")
|
|
|
|
|
|
|
|
extensions := map[string]string{
|
|
|
|
"tar": "tar",
|
|
|
|
"zip": "zip",
|
|
|
|
"gz": "pgzip",
|
|
|
|
"lz4": "lz4",
|
|
|
|
}
|
|
|
|
|
|
|
|
result := re.FindAllString(config.OutputPath, -1)
|
|
|
|
|
|
|
|
// Should we make an archive? E.g. tar or zip?
|
|
|
|
if result[0] == "tar" {
|
|
|
|
config.Archive = "tar"
|
|
|
|
}
|
|
|
|
if result[1] == "zip" || result[1] == "tar" {
|
|
|
|
config.Archive = result[1]
|
|
|
|
// Tar or zip is our final artifact. Bail out.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should we compress the artifact?
|
|
|
|
algorithm, ok := extensions[result[1]]
|
|
|
|
if ok {
|
|
|
|
config.Algorithm = algorithm
|
|
|
|
// We found our compression algorithm something. Bail out.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We didn't find anything. Default to tar + pgzip
|
|
|
|
config.Algorithm = "pgzip"
|
|
|
|
config.Archive = "tar"
|
|
|
|
return fmt.Errorf("Unable to detect compression algorithm")
|
|
|
|
}
|
|
|
|
|
2015-06-10 16:46:21 -04:00
|
|
|
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
2015-06-18 03:47:33 -04:00
|
|
|
p.config.Level = -1
|
2015-06-12 20:25:09 -04:00
|
|
|
err := config.Decode(&p.config, &config.DecodeOpts{
|
2015-06-10 16:33:50 -04:00
|
|
|
Interpolate: true,
|
|
|
|
InterpolateFilter: &interpolate.RenderFilter{
|
2015-06-12 20:25:09 -04:00
|
|
|
Exclude: []string{},
|
2015-06-10 16:33:50 -04:00
|
|
|
},
|
|
|
|
}, raws...)
|
2014-04-26 12:22:10 -04:00
|
|
|
|
2015-06-10 15:30:18 -04:00
|
|
|
errs := new(packer.MultiError)
|
|
|
|
|
2015-06-12 20:25:09 -04:00
|
|
|
if p.config.OutputPath == "" {
|
|
|
|
p.config.OutputPath = "packer_{{.BuildName}}_{{.Provider}}"
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
2015-06-12 20:25:09 -04:00
|
|
|
if err = interpolate.Validate(p.config.OutputPath, p.config.ctx); err != nil {
|
2015-06-10 15:30:18 -04:00
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, fmt.Errorf("Error parsing target template: %s", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
templates := map[string]*string{
|
2015-06-12 20:25:09 -04:00
|
|
|
"output": &p.config.OutputPath,
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
if p.config.Level > gzip.BestCompression {
|
|
|
|
p.config.Level = gzip.BestCompression
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
if p.config.Level == -1 {
|
|
|
|
p.config.Level = gzip.DefaultCompression
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for key, ptr := range templates {
|
|
|
|
if *ptr == "" {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, fmt.Errorf("%s must be set", key))
|
|
|
|
}
|
|
|
|
|
2015-06-12 20:25:09 -04:00
|
|
|
*ptr, err = interpolate.Render(p.config.OutputPath, p.config.ctx)
|
2015-06-10 15:30:18 -04:00
|
|
|
if err != nil {
|
|
|
|
errs = packer.MultiErrorAppend(
|
|
|
|
errs, fmt.Errorf("Error processing %s: %s", key, err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(errs.Errors) > 0 {
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
|
2014-04-26 12:22:10 -04:00
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
|
|
|
|
|
|
|
newArtifact := &Artifact{Path: p.config.OutputPath}
|
2014-04-26 12:22:10 -04:00
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
outputFile, err := os.Create(p.config.OutputPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, fmt.Errorf(
|
|
|
|
"Unable to create archive %s: %s", p.config.OutputPath, err)
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
defer outputFile.Close()
|
|
|
|
|
|
|
|
// Setup output interface. If we're using compression, output is a
|
|
|
|
// compression writer. Otherwise it's just a file.
|
|
|
|
var output io.WriteCloser
|
|
|
|
switch p.config.Algorithm {
|
|
|
|
case "lz4":
|
|
|
|
lzwriter := lz4.NewWriter(outputFile)
|
|
|
|
if p.config.Level > gzip.DefaultCompression {
|
|
|
|
lzwriter.Header.HighCompression = true
|
|
|
|
}
|
|
|
|
defer lzwriter.Close()
|
|
|
|
output = lzwriter
|
|
|
|
case "pgzip":
|
|
|
|
output, err = pgzip.NewWriterLevel(outputFile, p.config.Level)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, ErrInvalidCompressionLevel
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
defer output.Close()
|
|
|
|
default:
|
|
|
|
output = outputFile
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
|
|
|
|
//Archive
|
|
|
|
switch p.config.Archive {
|
|
|
|
case "tar":
|
|
|
|
archiveTar(artifact.Files(), output)
|
|
|
|
case "zip":
|
|
|
|
archive := zip.NewWriter(output)
|
|
|
|
defer archive.Close()
|
|
|
|
default:
|
|
|
|
// We have a regular file, so we'll just do an io.Copy
|
|
|
|
if len(artifact.Files()) != 1 {
|
|
|
|
return nil, false, fmt.Errorf(
|
|
|
|
"Can only have 1 input file when not using tar/zip. Found %d "+
|
|
|
|
"files: %v", len(artifact.Files()), artifact.Files())
|
|
|
|
}
|
|
|
|
source, err := os.Open(artifact.Files()[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, fmt.Errorf(
|
|
|
|
"Failed to open source file %s for reading: %s",
|
|
|
|
artifact.Files()[0], err)
|
|
|
|
}
|
|
|
|
defer source.Close()
|
|
|
|
io.Copy(output, source)
|
|
|
|
}
|
|
|
|
|
|
|
|
return newArtifact, p.config.KeepInputArtifact, nil
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
func archiveTar(files []string, output io.WriteCloser) error {
|
|
|
|
archive := tar.NewWriter(output)
|
|
|
|
defer archive.Close()
|
2015-06-10 15:30:18 -04:00
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
for _, path := range files {
|
|
|
|
file, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to read file %s: %s", path, err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
2015-06-10 15:30:18 -04:00
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
fi, err := file.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to get fileinfo for %s: %s", path, err)
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
|
|
|
|
target, err := os.Readlink(path)
|
2015-06-10 15:30:18 -04:00
|
|
|
if err != nil {
|
2015-06-18 03:47:33 -04:00
|
|
|
return fmt.Errorf("Failed to readlink for %s: %s", path, err)
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
header, err := tar.FileInfoHeader(fi, target)
|
2015-06-10 15:30:18 -04:00
|
|
|
if err != nil {
|
2015-06-18 03:47:33 -04:00
|
|
|
return fmt.Errorf("Failed to create tar header for %s: %s", path, err)
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
if err := archive.WriteHeader(header); err != nil {
|
|
|
|
return fmt.Errorf("Failed to write tar header for %s: %s", path, err)
|
|
|
|
}
|
2015-06-10 15:30:18 -04:00
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
if _, err := io.Copy(archive, file); err != nil {
|
|
|
|
return fmt.Errorf("Failed to copy %s data to archive: %s", path, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
func (p *PostProcessor) cmpTAR(files []string, target string) ([]string, error) {
|
|
|
|
fw, err := os.Create(target)
|
2015-06-10 15:30:18 -04:00
|
|
|
if err != nil {
|
2015-06-18 03:47:33 -04:00
|
|
|
return nil, fmt.Errorf("tar error creating tar %s: %s", target, err)
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
|
|
|
defer fw.Close()
|
|
|
|
|
2015-06-10 15:30:18 -04:00
|
|
|
tw := tar.NewWriter(fw)
|
|
|
|
defer tw.Close()
|
2014-04-26 12:22:10 -04:00
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
for _, name := range files {
|
2015-06-10 15:30:18 -04:00
|
|
|
fi, err := os.Stat(name)
|
2014-04-26 12:22:10 -04:00
|
|
|
if err != nil {
|
2015-06-12 20:25:09 -04:00
|
|
|
return nil, fmt.Errorf("tar error on stat of %s: %s", name, err)
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
|
|
|
|
2015-06-16 22:08:22 -04:00
|
|
|
target, _ := os.Readlink(name)
|
2014-04-26 12:22:10 -04:00
|
|
|
header, err := tar.FileInfoHeader(fi, target)
|
|
|
|
if err != nil {
|
2015-06-12 20:25:09 -04:00
|
|
|
return nil, fmt.Errorf("tar error reading info for %s: %s", name, err)
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
|
|
|
|
2015-06-10 15:30:18 -04:00
|
|
|
if err = tw.WriteHeader(header); err != nil {
|
2015-06-12 20:25:09 -04:00
|
|
|
return nil, fmt.Errorf("tar error writing header for %s: %s", name, err)
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
|
|
|
|
2015-06-10 15:30:18 -04:00
|
|
|
fr, err := os.Open(name)
|
2014-04-26 12:22:10 -04:00
|
|
|
if err != nil {
|
2015-06-12 20:25:09 -04:00
|
|
|
return nil, fmt.Errorf("tar error opening file %s: %s", name, err)
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = io.Copy(tw, fr); err != nil {
|
2015-06-10 15:30:18 -04:00
|
|
|
fr.Close()
|
2015-06-12 20:25:09 -04:00
|
|
|
return nil, fmt.Errorf("tar error copying contents of %s: %s", name, err)
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
fr.Close()
|
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
return []string{target}, nil
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
func (p *PostProcessor) cmpGZIP(files []string, target string) ([]string, error) {
|
2015-06-10 15:30:18 -04:00
|
|
|
var res []string
|
2015-06-18 03:47:33 -04:00
|
|
|
for _, name := range files {
|
|
|
|
filename := filepath.Join(target, filepath.Base(name))
|
2015-06-10 15:30:18 -04:00
|
|
|
fw, err := os.Create(filename)
|
|
|
|
if err != nil {
|
2015-06-18 03:47:33 -04:00
|
|
|
return nil, fmt.Errorf("gzip error creating archive: %s", err)
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
cw, err := gzip.NewWriterLevel(fw, p.config.Level)
|
2015-06-10 15:30:18 -04:00
|
|
|
if err != nil {
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("gzip error: %s", err)
|
|
|
|
}
|
|
|
|
fr, err := os.Open(name)
|
|
|
|
if err != nil {
|
|
|
|
cw.Close()
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("gzip error: %s", err)
|
|
|
|
}
|
|
|
|
if _, err = io.Copy(cw, fr); err != nil {
|
|
|
|
cw.Close()
|
|
|
|
fr.Close()
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("gzip error: %s", err)
|
|
|
|
}
|
|
|
|
cw.Close()
|
|
|
|
fr.Close()
|
|
|
|
fw.Close()
|
|
|
|
res = append(res, filename)
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2015-06-18 03:47:33 -04:00
|
|
|
func (p *PostProcessor) cmpPGZIP(files []string, target string) ([]string, error) {
|
2015-06-10 15:30:18 -04:00
|
|
|
var res []string
|
2015-06-18 03:47:33 -04:00
|
|
|
for _, name := range files {
|
|
|
|
filename := filepath.Join(target, filepath.Base(name))
|
2015-06-10 15:30:18 -04:00
|
|
|
fw, err := os.Create(filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("pgzip error: %s", err)
|
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
cw, err := pgzip.NewWriterLevel(fw, p.config.Level)
|
|
|
|
cw.SetConcurrency(500000, runtime.GOMAXPROCS(-1))
|
2015-06-10 15:30:18 -04:00
|
|
|
if err != nil {
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("pgzip error: %s", err)
|
|
|
|
}
|
|
|
|
fr, err := os.Open(name)
|
|
|
|
if err != nil {
|
|
|
|
cw.Close()
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("pgzip error: %s", err)
|
|
|
|
}
|
|
|
|
if _, err = io.Copy(cw, fr); err != nil {
|
|
|
|
cw.Close()
|
|
|
|
fr.Close()
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("pgzip error: %s", err)
|
|
|
|
}
|
|
|
|
cw.Close()
|
|
|
|
fr.Close()
|
|
|
|
fw.Close()
|
|
|
|
res = append(res, filename)
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2015-06-10 16:46:21 -04:00
|
|
|
func (p *PostProcessor) cmpLZ4(src []string, dst string) ([]string, error) {
|
2015-06-10 15:30:18 -04:00
|
|
|
var res []string
|
|
|
|
for _, name := range src {
|
|
|
|
filename := filepath.Join(dst, filepath.Base(name))
|
|
|
|
fw, err := os.Create(filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("lz4 error: %s", err)
|
|
|
|
}
|
|
|
|
cw := lz4.NewWriter(fw)
|
|
|
|
if err != nil {
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("lz4 error: %s", err)
|
|
|
|
}
|
2015-06-18 03:47:33 -04:00
|
|
|
if p.config.Level > gzip.DefaultCompression {
|
2015-06-10 15:30:18 -04:00
|
|
|
cw.Header.HighCompression = true
|
|
|
|
}
|
|
|
|
fr, err := os.Open(name)
|
|
|
|
if err != nil {
|
|
|
|
cw.Close()
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("lz4 error: %s", err)
|
|
|
|
}
|
|
|
|
if _, err = io.Copy(cw, fr); err != nil {
|
|
|
|
cw.Close()
|
|
|
|
fr.Close()
|
|
|
|
fw.Close()
|
|
|
|
return nil, fmt.Errorf("lz4 error: %s", err)
|
|
|
|
}
|
|
|
|
cw.Close()
|
|
|
|
fr.Close()
|
|
|
|
fw.Close()
|
|
|
|
res = append(res, filename)
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2015-06-10 16:46:21 -04:00
|
|
|
func (p *PostProcessor) cmpZIP(src []string, dst string) ([]string, error) {
|
2015-06-10 15:30:18 -04:00
|
|
|
fw, err := os.Create(dst)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("zip error: %s", err)
|
|
|
|
}
|
|
|
|
defer fw.Close()
|
|
|
|
|
|
|
|
zw := zip.NewWriter(fw)
|
|
|
|
defer zw.Close()
|
|
|
|
|
|
|
|
for _, name := range src {
|
|
|
|
header, err := zw.Create(name)
|
|
|
|
if err != nil {
|
2015-06-10 16:46:21 -04:00
|
|
|
return nil, fmt.Errorf("zip error: %s", err)
|
2015-06-10 15:30:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fr, err := os.Open(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("zip error: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = io.Copy(header, fr); err != nil {
|
|
|
|
fr.Close()
|
|
|
|
return nil, fmt.Errorf("zip error: %s", err)
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
2015-06-10 15:30:18 -04:00
|
|
|
fr.Close()
|
2014-04-26 12:22:10 -04:00
|
|
|
}
|
2015-06-10 15:30:18 -04:00
|
|
|
return []string{dst}, nil
|
2014-04-26 12:22:10 -04:00
|
|
|
|
|
|
|
}
|