initial import of checksum post-processor (#3492)

Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
This commit is contained in:
Vasiliy Tolstov 2016-05-19 03:31:16 +03:00 committed by Chris Bednarski
parent c1e7caf53c
commit d64603e137
7 changed files with 355 additions and 0 deletions

View File

@ -33,6 +33,7 @@ import (
amazonimportpostprocessor "github.com/mitchellh/packer/post-processor/amazon-import"
artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice"
atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas"
checksumpostprocessor "github.com/mitchellh/packer/post-processor/checksum"
compresspostprocessor "github.com/mitchellh/packer/post-processor/compress"
dockerimportpostprocessor "github.com/mitchellh/packer/post-processor/docker-import"
dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push"
@ -101,6 +102,7 @@ var PostProcessors = map[string]packer.PostProcessor{
"amazon-import": new(amazonimportpostprocessor.PostProcessor),
"artifice": new(artificepostprocessor.PostProcessor),
"atlas": new(atlaspostprocessor.PostProcessor),
"checksum": new(checksumpostprocessor.PostProcessor),
"compress": new(compresspostprocessor.PostProcessor),
"docker-import": new(dockerimportpostprocessor.PostProcessor),
"docker-push": new(dockerpushpostprocessor.PostProcessor),

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 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.

View File

@ -0,0 +1,48 @@
package checksum
import (
"fmt"
"os"
"strings"
)
const BuilderId = "packer.post-processor.checksum"
type Artifact struct {
files []string
}
func NewArtifact(files []string) *Artifact {
return &Artifact{files: files}
}
func (a *Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return a.files
}
func (a *Artifact) Id() string {
return ""
}
func (a *Artifact) String() string {
files := strings.Join(a.files, ", ")
return fmt.Sprintf("Created artifact from files: %s", files)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
for _, f := range a.files {
err := os.RemoveAll(f)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,129 @@
package checksum
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"io"
"os"
"path/filepath"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Keep bool `mapstructure:"keep_input_artifact"`
ChecksumTypes []string `mapstructure:"checksum_types"`
OutputPath string `mapstructure:"output"`
ctx interpolate.Context
}
type PostProcessor struct {
config Config
}
func (p *PostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{},
},
}, raws...)
if err != nil {
return err
}
if p.config.ChecksumTypes == nil {
p.config.ChecksumTypes = []string{"md5"}
}
if p.config.OutputPath == "" {
p.config.OutputPath = "packer_{{.BuildName}}_{{.BuilderType}}" + ".checksum"
}
errs := new(packer.MultiError)
if err = interpolate.Validate(p.config.OutputPath, &p.config.ctx); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing target template: %s", err))
}
if len(errs.Errors) > 0 {
return errs
}
return nil
}
func getHash(t string) hash.Hash {
var h hash.Hash
switch t {
case "md5":
h = md5.New()
case "sha1":
h = sha1.New()
case "sha224":
h = sha256.New224()
case "sha256":
h = sha256.New()
case "sha384":
h = sha512.New384()
case "sha512":
h = sha512.New()
}
return h
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
files := artifact.Files()
var h hash.Hash
var checksumFile string
newartifact := NewArtifact(artifact.Files())
for _, ct := range p.config.ChecksumTypes {
h = getHash(ct)
for _, art := range files {
if len(artifact.Files()) > 1 {
checksumFile = filepath.Join(filepath.Dir(art), ct+"sums")
} else if p.config.OutputPath != "" {
checksumFile = p.config.OutputPath
} else {
checksumFile = fmt.Sprintf("%s.%s", art, ct+"sum")
}
if _, err := os.Stat(checksumFile); err != nil {
newartifact.files = append(newartifact.files, checksumFile)
}
fw, err := os.OpenFile(checksumFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0644))
if err != nil {
return nil, false, fmt.Errorf("unable to create file %s: %s", checksumFile, err.Error())
}
fr, err := os.Open(art)
if err != nil {
fw.Close()
return nil, false, fmt.Errorf("unable to open file %s: %s", art, err.Error())
}
if _, err = io.Copy(h, fr); err != nil {
fr.Close()
fw.Close()
return nil, false, fmt.Errorf("unable to compute %s hash for %s", ct, art)
}
fr.Close()
fw.WriteString(fmt.Sprintf("%x\t%s\n", h.Sum(nil), filepath.Base(art)))
fw.Close()
}
}
return newartifact, true, nil
}

View File

@ -0,0 +1,108 @@
package checksum
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/mitchellh/packer/builder/file"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template"
)
const expectedFileContents = "Hello world!"
func TestChecksumSHA1(t *testing.T) {
const config = `
{
"post-processors": [
{
"type": "checksum",
"checksum_types": ["sha1"],
"output": "sha1sums"
}
]
}
`
artifact := testChecksum(t, config)
defer artifact.Destroy()
f, err := os.Open("sha1sums")
if err != nil {
t.Errorf("Unable to read checksum file: %s", err)
}
if buf, _ := ioutil.ReadAll(f); !bytes.Equal(buf, []byte("d3486ae9136e7856bc42212385ea797094475802\tpackage.txt\n")) {
t.Errorf("Failed to compate checksum: %s\n%s", buf, "d3486ae9136e7856bc42212385ea797094475802 package.txt")
}
defer f.Close()
}
// Test Helpers
func setup(t *testing.T) (packer.Ui, packer.Artifact, error) {
// Create fake UI and Cache
ui := packer.TestUi(t)
cache := &packer.FileCache{CacheDir: os.TempDir()}
// Create config for file builder
const fileConfig = `{"builders":[{"type":"file","target":"package.txt","content":"Hello world!"}]}`
tpl, err := template.Parse(strings.NewReader(fileConfig))
if err != nil {
return nil, nil, fmt.Errorf("Unable to parse setup configuration: %s", err)
}
// Prepare the file builder
builder := file.Builder{}
warnings, err := builder.Prepare(tpl.Builders["file"].Config)
if len(warnings) > 0 {
for _, warn := range warnings {
return nil, nil, fmt.Errorf("Configuration warning: %s", warn)
}
}
if err != nil {
return nil, nil, fmt.Errorf("Invalid configuration: %s", err)
}
// Run the file builder
artifact, err := builder.Run(ui, nil, cache)
if err != nil {
return nil, nil, fmt.Errorf("Failed to build artifact: %s", err)
}
return ui, artifact, err
}
func testChecksum(t *testing.T, config string) packer.Artifact {
ui, artifact, err := setup(t)
if err != nil {
t.Fatalf("Error bootstrapping test: %s", err)
}
if artifact != nil {
defer artifact.Destroy()
}
tpl, err := template.Parse(strings.NewReader(config))
if err != nil {
t.Fatalf("Unable to parse test config: %s", err)
}
checksum := PostProcessor{}
checksum.Configure(tpl.PostProcessors[0][0].Config)
// I get the feeling these should be automatically available somewhere, but
// some of the post-processors construct this manually.
checksum.config.ctx.BuildName = "chocolate"
checksum.config.PackerBuildName = "vanilla"
checksum.config.PackerBuilderType = "file"
artifactOut, _, err := checksum.PostProcess(ui, artifact)
if err != nil {
t.Fatalf("Failed to checksum artifact: %s", err)
}
return artifactOut
}

View File

@ -0,0 +1,46 @@
---
description: |
The checksum post-processor computes specified checksum for the artifact list
from an upstream builder or post-processor. All downstream post-processors will
see the new artifacts. The primary use-case is compute checksum for artifacts
allows to verify it later.
So firstly this post-processor get artifact, compute it checksum and pass to
next post-processor original artifacts and checksum files.
layout: docs
page_title: 'Checksum Post-Processor'
...
# Checksum Post-Processor
Type: `checksum`
The checksum post-processor computes specified checksum for the artifact list
from an upstream builder or post-processor. All downstream post-processors will
see the new artifacts. The primary use-case is compute checksum for artifact to
verify it later.
After computes checksum for artifacts, you can use new artifacts with other
post-processors like
[artifice](https://www.packer.io/docs/post-processors/artifice.html),
[compress](https://www.packer.io/docs/post-processors/compress.html),
[docker-push](https://www.packer.io/docs/post-processors/docker-push.html),
[atlas](https://www.packer.io/docs/post-processors/atlas.html), or a third-party
post-processor.
## Basic example
The example below is fully functional.
``` {.javascript}
{
"type": "checksum"
}
```
## Configuration Reference
Optional parameters:
- `checksum_types` (array of strings) - An array of strings of checksum types
to compute. Allowed values are md5, sha1, sha224, sha256, sha384, sha512.
- `output` (string) - Specify filename to store checksums.

View File

@ -75,6 +75,7 @@
<li><a href="/docs/post-processors/artifice.html">Artifice</a></li>
<li><a href="/docs/post-processors/atlas.html">Atlas</a></li>
<li><a href="/docs/post-processors/compress.html">compress</a></li>
<li><a href="/docs/post-processors/checksum.html">checksum</a></li>
<li><a href="/docs/post-processors/docker-import.html">docker-import</a></li>
<li><a href="/docs/post-processors/docker-push.html">docker-push</a></li>
<li><a href="/docs/post-processors/docker-save.html">docker-save</a></li>