diff --git a/command/plugin.go b/command/plugin.go index 95c08dc63..729edeba9 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -39,6 +39,7 @@ import ( dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push" dockersavepostprocessor "github.com/mitchellh/packer/post-processor/docker-save" dockertagpostprocessor "github.com/mitchellh/packer/post-processor/docker-tag" + manifestpostprocessor "github.com/mitchellh/packer/post-processor/manifest" shelllocalpostprocessor "github.com/mitchellh/packer/post-processor/shell-local" vagrantpostprocessor "github.com/mitchellh/packer/post-processor/vagrant" vagrantcloudpostprocessor "github.com/mitchellh/packer/post-processor/vagrant-cloud" @@ -108,6 +109,7 @@ var PostProcessors = map[string]packer.PostProcessor{ "docker-push": new(dockerpushpostprocessor.PostProcessor), "docker-save": new(dockersavepostprocessor.PostProcessor), "docker-tag": new(dockertagpostprocessor.PostProcessor), + "manifest": new(manifestpostprocessor.PostProcessor), "shell-local": new(shelllocalpostprocessor.PostProcessor), "vagrant": new(vagrantpostprocessor.PostProcessor), "vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor), diff --git a/post-processor/manifest/artifact.go b/post-processor/manifest/artifact.go new file mode 100644 index 000000000..984330e0e --- /dev/null +++ b/post-processor/manifest/artifact.go @@ -0,0 +1,37 @@ +package manifest + +import "fmt" + +const BuilderId = "packer.post-processor.manifest" + +type Artifact struct { + BuildName string `json:"build_name"` + BuildTime int64 `json:"build_time"` + Description string `json:"description"` + BuildFiles []string `json:"files"` + BuildId string `json:"artifact_id"` +} + +func (a *Artifact) BuilderId() string { + return BuilderId +} + +func (a *Artifact) Files() []string { + return a.BuildFiles +} + +func (a *Artifact) Id() string { + return a.BuildId +} + +func (a *Artifact) String() string { + return fmt.Sprintf("%s-%s", a.BuildName, a.BuildId) +} + +func (a *Artifact) State(name string) interface{} { + return nil +} + +func (a *Artifact) Destroy() error { + return nil +} diff --git a/post-processor/manifest/post-processor.go b/post-processor/manifest/post-processor.go new file mode 100644 index 000000000..2f8eadaee --- /dev/null +++ b/post-processor/manifest/post-processor.go @@ -0,0 +1,100 @@ +package manifest + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "time" + + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/helper/config" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" +) + +// The artifact-override post-processor allows you to specify arbitrary files as +// artifacts. These will override any other artifacts created by the builder. +// This allows you to use a builder and provisioner to create some file, such as +// a compiled binary or tarball, extract it from the builder (VM or container) +// and then save that binary or tarball and throw away the builder. + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + Filename string `mapstructure:"filename"` + + ctx interpolate.Context +} + +type PostProcessor struct { + config Config +} + +type ManifestFile struct { + Builds []Artifact `json:"builds"` +} + +func (p *PostProcessor) Configure(raws ...interface{}) error { + err := config.Decode(&p.config, &config.DecodeOpts{ + Interpolate: true, + InterpolateContext: &p.config.ctx, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{}, + }, + }, raws...) + if err != nil { + return err + } + + if p.config.Filename == "" { + p.config.Filename = "packer-manifest.json" + } + + return nil +} + +func (p *PostProcessor) PostProcess(ui packer.Ui, source packer.Artifact) (packer.Artifact, bool, error) { + artifact := &Artifact{} + + // Create the current artifact. + artifact.BuildFiles = source.Files() + artifact.BuildId = source.Id() + artifact.BuildName = source.BuilderId() + artifact.BuildTime = time.Now().Unix() + artifact.Description = source.String() + + // Create a lock file with exclusive access. If this fails we will retry + // after a delay + lockFilename := p.config.Filename + ".lock" + _, err := os.OpenFile(lockFilename, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + defer os.Remove(lockFilename) + + // Read the current manifest file from disk + contents := []byte{} + if contents, err = ioutil.ReadFile(p.config.Filename); err != nil && !os.IsNotExist(err) { + return nil, true, fmt.Errorf("Unable to open %s for reading: %s", p.config.Filename, err) + } + + // Parse the manifest file JSON, if we have some + manifestFile := &ManifestFile{} + if len(contents) > 0 { + if err = json.Unmarshal(contents, manifestFile); err != nil { + return nil, true, fmt.Errorf("Unable to parse content from %s: %s", p.config.Filename, err) + } + } + + // Add the current artifact to the manifest file + manifestFile.Builds = append(manifestFile.Builds, *artifact) + + // Write JSON to disk + if out, err := json.Marshal(manifestFile); err == nil { + if err := ioutil.WriteFile(p.config.Filename, out, 0664); err != nil { + return nil, true, fmt.Errorf("Unable to write %s: %s", p.config.Filename, err) + } + } else { + return nil, true, fmt.Errorf("Unable to marshal JSON %s", err) + } + + return artifact, true, err +}