Commit 52269b66 authored by Chris Bednarski's avatar Chris Bednarski

Added new compress post-processor, contributed by Vasiliy Tolstov

parent f3aed781
The MIT License (MIT)
Copyright (c) 2014 Vasiliy Tolstov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
...@@ -5,40 +5,34 @@ import ( ...@@ -5,40 +5,34 @@ import (
"os" "os"
) )
const BuilderId = "packer.post-processor.compress" const BuilderId = "vtolstov.compress"
type Artifact struct { type Artifact struct {
Path string builderId string
Provider string dir string
f []string
} }
func NewArtifact(provider, path string) *Artifact { func (a *Artifact) BuilderId() string {
return &Artifact{
Path: path,
Provider: provider,
}
}
func (*Artifact) BuilderId() string {
return BuilderId return BuilderId
} }
func (self *Artifact) Id() string { func (a *Artifact) Files() []string {
return "" return a.f
} }
func (self *Artifact) Files() []string { func (*Artifact) Id() string {
return []string{self.Path} return "COMPRESS"
} }
func (self *Artifact) String() string { func (a *Artifact) String() string {
return fmt.Sprintf("'%s' compressing: %s", self.Provider, self.Path) return fmt.Sprintf("VM compressed files in directory: %s", a.dir)
} }
func (*Artifact) State(name string) interface{} { func (*Artifact) State(name string) interface{} {
return nil return nil
} }
func (self *Artifact) Destroy() error { func (a *Artifact) Destroy() error {
return os.Remove(self.Path) return os.RemoveAll(a.dir)
} }
// +build ignore
package main
import (
"compress/flate"
gzip "compress/gzip"
"io"
"io/ioutil"
"fmt"
"os"
"runtime"
"testing"
bgzf "github.com/biogo/hts/bgzf"
pgzip "github.com/klauspost/pgzip"
lz4 "github.com/pierrec/lz4"
)
type Compressor struct {
r *os.File
w *os.File
sr int64
sw int64
}
func (c *Compressor) Close() error {
var err error
fi, _ := c.w.Stat()
c.sw = fi.Size()
if err = c.w.Close(); err != nil {
return err
}
fi, _ = c.r.Stat()
c.sr = fi.Size()
if err = c.r.Close(); err != nil {
return err
}
return nil
}
func NewCompressor(src, dst string) (*Compressor, error) {
r, err := os.Open(src)
if err != nil {
return nil, err
}
w, err := os.Create(dst)
if err != nil {
r.Close()
return nil, err
}
c := &Compressor{r: r, w: w}
return c, nil
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
var resw testing.BenchmarkResult
var resr testing.BenchmarkResult
c, err := NewCompressor("/tmp/image.r", "/tmp/image.w")
if err != nil {
panic(err)
}
resw = testing.Benchmark(c.BenchmarkGZIPWriter)
c.w.Seek(0, 0)
resr = testing.Benchmark(c.BenchmarkGZIPReader)
c.Close()
fmt.Printf("gzip:\twriter %s\treader %s\tsize %d\n", resw.T.String(), resr.T.String(), c.sw)
c, err = NewCompressor("/tmp/image.r", "/tmp/image.w")
if err != nil {
panic(err)
}
resw = testing.Benchmark(c.BenchmarkBGZFWriter)
c.w.Seek(0, 0)
resr = testing.Benchmark(c.BenchmarkBGZFReader)
c.Close()
fmt.Printf("bgzf:\twriter %s\treader %s\tsize %d\n", resw.T.String(), resr.T.String(), c.sw)
c, err = NewCompressor("/tmp/image.r", "/tmp/image.w")
if err != nil {
panic(err)
}
resw = testing.Benchmark(c.BenchmarkPGZIPWriter)
c.w.Seek(0, 0)
resr = testing.Benchmark(c.BenchmarkPGZIPReader)
c.Close()
fmt.Printf("pgzip:\twriter %s\treader %s\tsize %d\n", resw.T.String(), resr.T.String(), c.sw)
c, err = NewCompressor("/tmp/image.r", "/tmp/image.w")
if err != nil {
panic(err)
}
resw = testing.Benchmark(c.BenchmarkLZ4Writer)
c.w.Seek(0, 0)
resr = testing.Benchmark(c.BenchmarkLZ4Reader)
c.Close()
fmt.Printf("lz4:\twriter %s\treader %s\tsize %d\n", resw.T.String(), resr.T.String(), c.sw)
}
func (c *Compressor) BenchmarkGZIPWriter(b *testing.B) {
cw, _ := gzip.NewWriterLevel(c.w, flate.BestSpeed)
b.ResetTimer()
_, err := io.Copy(cw, c.r)
if err != nil {
b.Fatal(err)
}
cw.Close()
c.w.Sync()
}
func (c *Compressor) BenchmarkGZIPReader(b *testing.B) {
cr, _ := gzip.NewReader(c.w)
b.ResetTimer()
_, err := io.Copy(ioutil.Discard, cr)
if err != nil {
b.Fatal(err)
}
}
func (c *Compressor) BenchmarkBGZFWriter(b *testing.B) {
cw, _ := bgzf.NewWriterLevel(c.w, flate.BestSpeed, runtime.NumCPU())
b.ResetTimer()
_, err := io.Copy(cw, c.r)
if err != nil {
b.Fatal(err)
}
c.w.Sync()
}
func (c *Compressor) BenchmarkBGZFReader(b *testing.B) {
cr, _ := bgzf.NewReader(c.w, 0)
b.ResetTimer()
_, err := io.Copy(ioutil.Discard, cr)
if err != nil {
b.Fatal(err)
}
}
func (c *Compressor) BenchmarkPGZIPWriter(b *testing.B) {
cw, _ := pgzip.NewWriterLevel(c.w, flate.BestSpeed)
b.ResetTimer()
_, err := io.Copy(cw, c.r)
if err != nil {
b.Fatal(err)
}
cw.Close()
c.w.Sync()
}
func (c *Compressor) BenchmarkPGZIPReader(b *testing.B) {
cr, _ := pgzip.NewReader(c.w)
b.ResetTimer()
_, err := io.Copy(ioutil.Discard, cr)
if err != nil {
b.Fatal(err)
}
}
func (c *Compressor) BenchmarkLZ4Writer(b *testing.B) {
cw := lz4.NewWriter(c.w)
// cw.Header.HighCompression = true
cw.Header.NoChecksum = true
b.ResetTimer()
_, err := io.Copy(cw, c.r)
if err != nil {
b.Fatal(err)
}
cw.Close()
c.w.Sync()
}
func (c *Compressor) BenchmarkLZ4Reader(b *testing.B) {
cr := lz4.NewReader(c.w)
b.ResetTimer()
_, err := io.Copy(ioutil.Discard, cr)
if err != nil {
b.Fatal(err)
}
}
package compress package compress
import ( import (
"archive/tar" tar "archive/tar"
"compress/gzip" zip "archive/zip"
"compress/flate"
gzip "compress/gzip"
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"runtime"
"strings"
"time"
bgzf "github.com/biogo/hts/bgzf"
pgzip "github.com/klauspost/pgzip"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" lz4 "github.com/pierrec/lz4"
"gopkg.in/yaml.v2"
) )
type Metadata map[string]Metaitem
type Metaitem struct {
CompSize int64 `yaml:"compsize"`
OrigSize int64 `yaml:"origsize"`
CompType string `yaml:"comptype"`
CompDate string `yaml:"compdate"`
}
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
OutputPath string `mapstructure:"output"` OutputPath string `mapstructure:"output"`
OutputFile string `mapstructure:"file"`
ctx interpolate.Context Compression int `mapstructure:"compression"`
Metadata bool `mapstructure:"metadata"`
NumCPU int `mapstructure:"numcpu"`
Format string `mapstructure:"format"`
KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
tpl *packer.ConfigTemplate
} }
type PostProcessor struct { type CompressPostProcessor struct {
config Config cfg Config
} }
func (self *PostProcessor) Configure(raws ...interface{}) error { func (p *CompressPostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&self.config, &config.DecodeOpts{ p.cfg.Compression = -1
Interpolate: true, _, err := common.DecodeConfig(&p.cfg, raws...)
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{},
},
}, raws...)
if err != nil { if err != nil {
return err return err
} }
errs := new(packer.MultiError)
p.cfg.tpl, err = packer.NewConfigTemplate()
if err != nil {
return err
}
p.cfg.tpl.UserVars = p.cfg.PackerUserVars
if p.cfg.OutputPath == "" {
p.cfg.OutputPath = "packer_{{.BuildName}}_{{.Provider}}"
}
if err = p.cfg.tpl.Validate(p.cfg.OutputPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing target template: %s", err))
}
templates := map[string]*string{
"output": &p.cfg.OutputPath,
}
if p.cfg.Compression > flate.BestCompression {
p.cfg.Compression = flate.BestCompression
}
if p.cfg.Compression == -1 {
p.cfg.Compression = flate.DefaultCompression
}
if p.cfg.NumCPU < 1 {
p.cfg.NumCPU = runtime.NumCPU()
}
runtime.GOMAXPROCS(p.cfg.NumCPU)
for key, ptr := range templates {
if *ptr == "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", key))
}
*ptr, err = p.cfg.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error processing %s: %s", key, err))
}
}
if len(errs.Errors) > 0 {
return errs
}
return nil return nil
} }
func (self *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { func (p *CompressPostProcessor) fillMetadata(metadata Metadata, files []string) Metadata {
ui.Say(fmt.Sprintf("Creating archive for '%s'", artifact.BuilderId())) // layout shows by example how the reference time should be represented.
const layout = "2006-01-02_15-04-05"
t := time.Now()
// Create the compressed archive file at the appropriate OutputPath. if !p.cfg.Metadata {
fw, err := os.Create(self.config.OutputPath) return metadata
}
for _, f := range files {
if fi, err := os.Stat(f); err != nil {
continue
} else {
if i, ok := metadata[filepath.Base(f)]; !ok {
metadata[filepath.Base(f)] = Metaitem{CompType: p.cfg.Format, OrigSize: fi.Size(), CompDate: t.Format(layout)}
} else {
i.CompSize = fi.Size()
i.CompDate = t.Format(layout)
metadata[filepath.Base(f)] = i
}
}
}
return metadata
}
func (p *CompressPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
newartifact := &Artifact{builderId: artifact.BuilderId(), dir: p.cfg.OutputPath}
var metafile string = filepath.Join(p.cfg.OutputPath, "metadata")
_, err := os.Stat(newartifact.dir)
if err == nil {
return nil, false, fmt.Errorf("output dir must not exists: %s", err)
}
err = os.MkdirAll(newartifact.dir, 0755)
if err != nil { if err != nil {
return nil, false, fmt.Errorf( return nil, false, fmt.Errorf("failed to create output: %s", err)
"Failed creating file for compressed archive: %s", self.config.OutputPath) }
formats := strings.Split(p.cfg.Format, ".")
files := artifact.Files()
metadata := make(Metadata, 0)
metadata = p.fillMetadata(metadata, files)
for _, compress := range formats {
switch compress {
case "tar":
files, err = p.cmpTAR(files, filepath.Join(p.cfg.OutputPath, p.cfg.OutputFile))
metadata = p.fillMetadata(metadata, files)
case "zip":
files, err = p.cmpZIP(files, filepath.Join(p.cfg.OutputPath, p.cfg.OutputFile))
metadata = p.fillMetadata(metadata, files)
case "pgzip":
files, err = p.cmpPGZIP(files, p.cfg.OutputPath)
metadata = p.fillMetadata(metadata, files)
case "gzip":
files, err = p.cmpGZIP(files, p.cfg.OutputPath)
metadata = p.fillMetadata(metadata, files)
case "bgzf":
files, err = p.cmpBGZF(files, p.cfg.OutputPath)
metadata = p.fillMetadata(metadata, files)
case "lz4":
files, err = p.cmpLZ4(files, p.cfg.OutputPath)
metadata = p.fillMetadata(metadata, files)
case "e2fs":
files, err = p.cmpE2FS(files, filepath.Join(p.cfg.OutputPath, p.cfg.OutputFile))
metadata = p.fillMetadata(metadata, files)
}
if err != nil {
return nil, false, fmt.Errorf("Failed to compress: %s", err)
}
}
if p.cfg.Metadata {
fp, err := os.Create(metafile)
if err != nil {
return nil, false, err
}
if buf, err := yaml.Marshal(metadata); err != nil {
fp.Close()
return nil, false, err
} else {
if _, err = fp.Write(buf); err != nil {
fp.Close()
return nil, false, err
}
fp.Close()
}
}
newartifact.f = append(newartifact.f, files...)
if p.cfg.Metadata {
newartifact.f = append(newartifact.f, metafile)
}
return newartifact, p.cfg.KeepInputArtifact, nil
}
func (p *CompressPostProcessor) cmpTAR(src []string, dst string) ([]string, error) {
fw, err := os.Create(dst)
if err != nil {
return nil, fmt.Errorf("tar error: %s", err)
} }
defer fw.Close() defer fw.Close()
gw := gzip.NewWriter(fw) tw := tar.NewWriter(fw)
defer gw.Close() defer tw.Close()
// Iterate through all of the artifact's files and put them into the for _, name := range src {
// compressed archive using the tar/gzip writers. fi, err := os.Stat(name)
for _, path := range artifact.Files() {
fi, err := os.Stat(path)
if err != nil { if err != nil {
return nil, false, fmt.Errorf( return nil, fmt.Errorf("tar error: %s", err)
"Failed stating file: %s", path)
} }
target, _ := os.Readlink(path) target, _ := os.Readlink(name)
header, err := tar.FileInfoHeader(fi, target) header, err := tar.FileInfoHeader(fi, target)
if err != nil { if err != nil {
return nil, false, fmt.Errorf( return nil, fmt.Errorf("tar erorr: %s", err)
"Failed creating archive header: %s", path)
} }
tw := tar.NewWriter(gw) if err = tw.WriteHeader(header); err != nil {
defer tw.Close() return nil, fmt.Errorf("tar error: %s", err)
// Write the header first to the archive. This takes partial data
// from the FileInfo that is grabbed by running the stat command.
if err := tw.WriteHeader(header); err != nil {
return nil, false, fmt.Errorf(
"Failed writing archive header: %s", path)
} }
// Open the target file for archiving and compressing. fr, err := os.Open(name)
fr, err := os.Open(path)
if err != nil { if err != nil {
return nil, false, fmt.Errorf( return nil, fmt.Errorf("tar error: %s", err)
"Failed opening file '%s' to write compressed archive.", path)
} }
defer fr.Close()
if _, err = io.Copy(tw, fr); err != nil { if _, err = io.Copy(tw, fr); err != nil {
return nil, false, fmt.Errorf( fr.Close()
"Failed copying file to archive: %s", path) return nil, fmt.Errorf("tar error: %s", err)
}
fr.Close()
}
return []string{dst}, nil
}
func (p *CompressPostProcessor) cmpGZIP(src []string, dst string) ([]string, error) {
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("gzip error: %s", err)
}
cw, err := gzip.NewWriterLevel(fw, p.cfg.Compression)
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
}
func (p *CompressPostProcessor) cmpPGZIP(src []string, dst string) ([]string, error) {
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("pgzip error: %s", err)
}
cw, err := pgzip.NewWriterLevel(fw, p.cfg.Compression)
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
}
func (p *CompressPostProcessor) cmpLZ4(src []string, dst string) ([]string, error) {
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)
}
if p.cfg.Compression > flate.DefaultCompression {
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
}
func (p *CompressPostProcessor) cmpBGZF(src []string, dst string) ([]string, error) {
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("bgzf error: %s", err)
}
cw, err := bgzf.NewWriterLevel(fw, p.cfg.Compression, runtime.NumCPU())
if err != nil {
return nil, fmt.Errorf("bgzf error: %s", err)
}
fr, err := os.Open(name)
if err != nil {
cw.Close()
fw.Close()
return nil, fmt.Errorf("bgzf error: %s", err)
}
if _, err = io.Copy(cw, fr); err != nil {
cw.Close()
fr.Close()
fw.Close()
return nil, fmt.Errorf("bgzf error: %s", err)
}
cw.Close()
fr.Close()
fw.Close()
res = append(res, filename)
}
return res, nil
}
func (p *CompressPostProcessor) cmpE2FS(src []string, dst string) ([]string, error) {
panic("not implemented")
}
func (p *CompressPostProcessor) cmpZIP(src []string, dst string) ([]string, error) {
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 {
return nil, fmt.Errorf("zip erorr: %s", err)
}
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)
} }
fr.Close()
} }
return []string{dst}, nil
return NewArtifact(artifact.BuilderId(), self.config.OutputPath), false, nil
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment