mirror of
https://github.com/hashicorp/packer.git
synced 2026-10-04 21:53:18 -04:00
Merge pull request #733 from mitchellh/f-vagrant-pp-revamp
Vagrant Post-Processor Refactor + "Include" feature
This commit is contained in:
@@ -49,15 +49,8 @@ func (c Command) Run(env packer.Environment, args []string) int {
|
||||
// Close the file since we're done with that
|
||||
tplF.Close()
|
||||
|
||||
// Run the template through the various fixers
|
||||
fixers := []string{
|
||||
"iso-md5",
|
||||
"createtime",
|
||||
"virtualbox-gaattach",
|
||||
}
|
||||
|
||||
input := templateData
|
||||
for _, name := range fixers {
|
||||
for _, name := range FixerOrder {
|
||||
var err error
|
||||
fixer, ok := Fixers[name]
|
||||
if !ok {
|
||||
|
||||
@@ -15,10 +15,21 @@ type Fixer interface {
|
||||
// Fixers is the map of all available fixers, by name.
|
||||
var Fixers map[string]Fixer
|
||||
|
||||
// FixerOrder is the default order the fixers should be run.
|
||||
var FixerOrder []string
|
||||
|
||||
func init() {
|
||||
Fixers = map[string]Fixer{
|
||||
"iso-md5": new(FixerISOMD5),
|
||||
"createtime": new(FixerCreateTime),
|
||||
"pp-vagrant-override": new(FixerVagrantPPOverride),
|
||||
"virtualbox-gaattach": new(FixerVirtualBoxGAAttach),
|
||||
}
|
||||
|
||||
FixerOrder = []string{
|
||||
"iso-md5",
|
||||
"createtime",
|
||||
"virtualbox-gaattach",
|
||||
"pp-vagrant-override",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// FixerVagrantPPOvveride is a Fixer that replaces the provider-specific
|
||||
// overrides for the Vagrant post-processor with the new style introduced
|
||||
// as part of Packer 0.5.0.
|
||||
type FixerVagrantPPOverride struct{}
|
||||
|
||||
func (FixerVagrantPPOverride) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
||||
// Our template type we'll use for this fixer only
|
||||
type template struct {
|
||||
PostProcessors []interface{} `mapstructure:"post-processors"`
|
||||
}
|
||||
|
||||
// Decode the input into our structure, if we can
|
||||
var tpl template
|
||||
if err := mapstructure.Decode(input, &tpl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Go through each post-processor and get out all the complex configs
|
||||
pps := make([]map[string]interface{}, 0, len(tpl.PostProcessors))
|
||||
for _, rawPP := range tpl.PostProcessors {
|
||||
switch pp := rawPP.(type) {
|
||||
case string:
|
||||
case map[string]interface{}:
|
||||
pps = append(pps, pp)
|
||||
case []interface{}:
|
||||
for _, innerRawPP := range pp {
|
||||
if innerPP, ok := innerRawPP.(map[string]interface{}); ok {
|
||||
pps = append(pps, innerPP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through each post-processor and make the fix if necessary
|
||||
possible := []string{"aws", "digitalocean", "virtualbox", "vmware"}
|
||||
for _, pp := range pps {
|
||||
typeRaw, ok := pp["type"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if typeName, ok := typeRaw.(string); !ok {
|
||||
continue
|
||||
} else if typeName != "vagrant" {
|
||||
continue
|
||||
}
|
||||
|
||||
overrides := make(map[string]interface{})
|
||||
for _, name := range possible {
|
||||
if _, ok := pp[name]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
overrides[name] = pp[name]
|
||||
delete(pp, name)
|
||||
}
|
||||
|
||||
if len(overrides) > 0 {
|
||||
pp["override"] = overrides
|
||||
}
|
||||
}
|
||||
|
||||
input["post-processors"] = tpl.PostProcessors
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (FixerVagrantPPOverride) Synopsis() string {
|
||||
return `Fixes provider-specific overrides for Vagrant post-processor`
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFixerVagrantPPOverride_Impl(t *testing.T) {
|
||||
var _ Fixer = new(FixerVagrantPPOverride)
|
||||
}
|
||||
|
||||
func TestFixerVagrantPPOverride_Fix(t *testing.T) {
|
||||
var f FixerVagrantPPOverride
|
||||
|
||||
input := map[string]interface{}{
|
||||
"post-processors": []interface{}{
|
||||
"foo",
|
||||
|
||||
map[string]interface{}{
|
||||
"type": "vagrant",
|
||||
"aws": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
|
||||
map[string]interface{}{
|
||||
"type": "vsphere",
|
||||
},
|
||||
|
||||
[]interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "vagrant",
|
||||
"vmware": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"post-processors": []interface{}{
|
||||
"foo",
|
||||
|
||||
map[string]interface{}{
|
||||
"type": "vagrant",
|
||||
"override": map[string]interface{}{
|
||||
"aws": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
map[string]interface{}{
|
||||
"type": "vsphere",
|
||||
},
|
||||
|
||||
[]interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "vagrant",
|
||||
"override": map[string]interface{}{
|
||||
"vmware": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
output, err := f.Fix(input)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(output, expected) {
|
||||
t.Fatalf("unexpected: %#v\nexpected: %#v\n", output, expected)
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -11,6 +11,11 @@ Usage: packer fix [options] TEMPLATE
|
||||
|
||||
Fixes that are run:
|
||||
|
||||
iso-md5 Replaces "iso_md5" in builders with newer "iso_checksum"
|
||||
iso-md5 Replaces "iso_md5" in builders with newer "iso_checksum"
|
||||
createtime Replaces ".CreateTime" in builder configs with "{{timestamp}}"
|
||||
virtualbox-gaattach Updates VirtualBox builders using "guest_additions_attach"
|
||||
to use "guest_additions_mode"
|
||||
pp-vagrant-override Replaces old-style provider overrides for the Vagrant
|
||||
post-processor to new-style as of Packer 0.5.0.
|
||||
|
||||
`
|
||||
|
||||
+16
-6
@@ -2,16 +2,26 @@ package packer
|
||||
|
||||
// MockArtifact is an implementation of Artifact that can be used for tests.
|
||||
type MockArtifact struct {
|
||||
IdValue string
|
||||
DestroyCalled bool
|
||||
BuilderIdValue string
|
||||
FilesValue []string
|
||||
IdValue string
|
||||
DestroyCalled bool
|
||||
}
|
||||
|
||||
func (*MockArtifact) BuilderId() string {
|
||||
return "bid"
|
||||
func (a *MockArtifact) BuilderId() string {
|
||||
if a.BuilderIdValue == "" {
|
||||
return "bid"
|
||||
}
|
||||
|
||||
return a.BuilderIdValue
|
||||
}
|
||||
|
||||
func (*MockArtifact) Files() []string {
|
||||
return []string{"a", "b"}
|
||||
func (a *MockArtifact) Files() []string {
|
||||
if a.FilesValue == nil {
|
||||
return []string{"a", "b"}
|
||||
}
|
||||
|
||||
return a.FilesValue
|
||||
}
|
||||
|
||||
func (a *MockArtifact) Id() string {
|
||||
|
||||
+21
-133
@@ -1,157 +1,45 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type AWSBoxConfig struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
type AWSProvider struct{}
|
||||
|
||||
OutputPath string `mapstructure:"output"`
|
||||
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
|
||||
CompressionLevel string `mapstructure:"compression_level"`
|
||||
func (p *AWSProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "aws"}
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type AWSVagrantfileTemplate struct {
|
||||
Images map[string]string
|
||||
}
|
||||
|
||||
type AWSBoxPostProcessor struct {
|
||||
config AWSBoxConfig
|
||||
}
|
||||
|
||||
func (p *AWSBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.config.tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
validates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
"vagrantfile_template": &p.config.VagrantfileTemplate,
|
||||
"compression_level": &p.config.CompressionLevel,
|
||||
}
|
||||
|
||||
for n, ptr := range validates {
|
||||
if err := p.config.tpl.Validate(*ptr); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
// Determine the regions...
|
||||
tplData := &AWSVagrantfileTemplate{
|
||||
// Build up the template data to build our Vagrantfile
|
||||
tplData := &awsVagrantfileTemplate{
|
||||
Images: make(map[string]string),
|
||||
}
|
||||
|
||||
for _, regions := range strings.Split(artifact.Id(), ",") {
|
||||
parts := strings.Split(regions, ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, false, fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
|
||||
err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
|
||||
return
|
||||
}
|
||||
|
||||
tplData.Images[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
// Compile the output path
|
||||
outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: p.config.PackerBuildName,
|
||||
Provider: "aws",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// Build up the contents
|
||||
var contents bytes.Buffer
|
||||
t := template.Must(template.New("vf").Parse(defaultAWSVagrantfile))
|
||||
err = t.Execute(&contents, tplData)
|
||||
vagrantfile = contents.String()
|
||||
return
|
||||
}
|
||||
|
||||
// Create a temporary directory for us to build the contents of the box in
|
||||
dir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Create the Vagrantfile from the template
|
||||
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer vf.Close()
|
||||
|
||||
vagrantfileContents := defaultAWSVagrantfile
|
||||
if p.config.VagrantfileTemplate != "" {
|
||||
log.Printf("Using vagrantfile template: %s", p.config.VagrantfileTemplate)
|
||||
f, err := os.Open(p.config.VagrantfileTemplate)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error opening vagrantfile template: %s", err)
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error reading vagrantfile template: %s", err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
vagrantfileContents = string(contents)
|
||||
}
|
||||
|
||||
vagrantfileContents, err = p.config.tpl.Process(vagrantfileContents, tplData)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Error writing Vagrantfile: %s", err)
|
||||
}
|
||||
vf.Write([]byte(vagrantfileContents))
|
||||
vf.Close()
|
||||
|
||||
var level int = flate.DefaultCompression
|
||||
if p.config.CompressionLevel != "" {
|
||||
level, err = strconv.Atoi(p.config.CompressionLevel)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create the metadata
|
||||
metadata := map[string]string{"provider": "aws"}
|
||||
if err := WriteMetadata(dir, metadata); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Compress the directory to the given output path
|
||||
if err := DirToBox(outputPath, dir, ui, level); err != nil {
|
||||
err = fmt.Errorf("error creating box: %s", err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return NewArtifact("aws", outputPath), true, nil
|
||||
type awsVagrantfileTemplate struct {
|
||||
Images map[string]string
|
||||
}
|
||||
|
||||
var defaultAWSVagrantfile = `
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAWSBoxPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &AWSBoxPostProcessor{}
|
||||
if _, ok := raw.(packer.PostProcessor); !ok {
|
||||
t.Fatalf("AWS PostProcessor should be a PostProcessor")
|
||||
}
|
||||
func TestAWSProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(AWSProvider)
|
||||
}
|
||||
|
||||
@@ -1,155 +1,41 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type DigitalOceanBoxConfig struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
OutputPath string `mapstructure:"output"`
|
||||
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
|
||||
CompressionLevel string `mapstructure:"compression_level"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type DigitalOceanVagrantfileTemplate struct {
|
||||
type digitalOceanVagrantfileTemplate struct {
|
||||
Image string ""
|
||||
Region string ""
|
||||
}
|
||||
|
||||
type DigitalOceanBoxPostProcessor struct {
|
||||
config DigitalOceanBoxConfig
|
||||
}
|
||||
type DigitalOceanProvider struct{}
|
||||
|
||||
func (p *DigitalOceanBoxPostProcessor) Configure(rDigitalOcean ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&p.config, rDigitalOcean...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (p *DigitalOceanProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "digital_ocean"}
|
||||
|
||||
p.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.config.tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
validates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
"vagrantfile_template": &p.config.VagrantfileTemplate,
|
||||
"compression_level": &p.config.CompressionLevel,
|
||||
}
|
||||
|
||||
for n, ptr := range validates {
|
||||
if err := p.config.tpl.Validate(*ptr); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DigitalOceanBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
// Determine the image and region...
|
||||
tplData := &DigitalOceanVagrantfileTemplate{}
|
||||
tplData := &digitalOceanVagrantfileTemplate{}
|
||||
|
||||
parts := strings.Split(artifact.Id(), ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, false, fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
|
||||
err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
|
||||
return
|
||||
}
|
||||
|
||||
tplData.Region = parts[0]
|
||||
tplData.Image = parts[1]
|
||||
|
||||
// Compile the output path
|
||||
outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: p.config.PackerBuildName,
|
||||
Provider: "digitalocean",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create a temporary directory for us to build the contents of the box in
|
||||
dir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Create the Vagrantfile from the template
|
||||
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer vf.Close()
|
||||
|
||||
vagrantfileContents := defaultDigitalOceanVagrantfile
|
||||
if p.config.VagrantfileTemplate != "" {
|
||||
log.Printf("Using vagrantfile template: %s", p.config.VagrantfileTemplate)
|
||||
f, err := os.Open(p.config.VagrantfileTemplate)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error opening vagrantfile template: %s", err)
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error reading vagrantfile template: %s", err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
vagrantfileContents = string(contents)
|
||||
}
|
||||
|
||||
vagrantfileContents, err = p.config.tpl.Process(vagrantfileContents, tplData)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Error writing Vagrantfile: %s", err)
|
||||
}
|
||||
vf.Write([]byte(vagrantfileContents))
|
||||
vf.Close()
|
||||
|
||||
// Create the metadata
|
||||
metadata := map[string]string{"provider": "digital_ocean"}
|
||||
if err := WriteMetadata(dir, metadata); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Compress the directory to the given output path
|
||||
var level int = flate.DefaultCompression
|
||||
if p.config.CompressionLevel != "" {
|
||||
level, err = strconv.Atoi(p.config.CompressionLevel)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := DirToBox(outputPath, dir, ui, level); err != nil {
|
||||
err = fmt.Errorf("error creating box: %s", err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return NewArtifact("DigitalOcean", outputPath), true, nil
|
||||
// Build up the Vagrantfile
|
||||
var contents bytes.Buffer
|
||||
t := template.Must(template.New("vf").Parse(defaultDigitalOceanVagrantfile))
|
||||
err = t.Execute(&contents, tplData)
|
||||
vagrantfile = contents.String()
|
||||
return
|
||||
}
|
||||
|
||||
var defaultDigitalOceanVagrantfile = `
|
||||
@@ -159,5 +45,4 @@ Vagrant.configure("2") do |config|
|
||||
digital_ocean.region = "{{ .Region }}"
|
||||
end
|
||||
end
|
||||
|
||||
`
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDigitalOceanBoxPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &DigitalOceanBoxPostProcessor{}
|
||||
if _, ok := raw.(packer.PostProcessor); !ok {
|
||||
t.Fatalf("Digitalocean PostProcessor should be a PostProcessor")
|
||||
}
|
||||
func TestDigitalOceanProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(DigitalOceanProvider)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,15 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
var builtins = map[string]string{
|
||||
@@ -22,133 +26,213 @@ var builtins = map[string]string{
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
OutputPath string `mapstructure:"output"`
|
||||
CompressionLevel int `mapstructure:"compression_level"`
|
||||
Include []string `mapstructure:"include"`
|
||||
OutputPath string `mapstructure:"output"`
|
||||
Override map[string]interface{}
|
||||
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type PostProcessor struct {
|
||||
config Config
|
||||
premade map[string]packer.PostProcessor
|
||||
extraConfig map[string]interface{}
|
||||
configs map[string]*Config
|
||||
}
|
||||
|
||||
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
_, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
p.configs = make(map[string]*Config)
|
||||
p.configs[""] = new(Config)
|
||||
if err := p.configureSingle(p.configs[""], raws...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tpl, err := packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tpl.UserVars = p.config.PackerUserVars
|
||||
// Go over any of the provider-specific overrides and load those up.
|
||||
for name, override := range p.configs[""].Override {
|
||||
subRaws := make([]interface{}, len(raws)+1)
|
||||
copy(subRaws, raws)
|
||||
subRaws[len(raws)] = override
|
||||
|
||||
// Defaults
|
||||
if p.config.OutputPath == "" {
|
||||
p.config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box"
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := new(packer.MultiError)
|
||||
if err := tpl.Validate(p.config.OutputPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing output template: %s", err))
|
||||
}
|
||||
|
||||
// Store extra configuration we'll send to each post-processor type
|
||||
p.extraConfig = make(map[string]interface{})
|
||||
p.extraConfig["output"] = p.config.OutputPath
|
||||
p.extraConfig["packer_build_name"] = p.config.PackerBuildName
|
||||
p.extraConfig["packer_builder_type"] = p.config.PackerBuilderType
|
||||
p.extraConfig["packer_debug"] = p.config.PackerDebug
|
||||
p.extraConfig["packer_force"] = p.config.PackerForce
|
||||
p.extraConfig["packer_user_variables"] = p.config.PackerUserVars
|
||||
|
||||
// TODO(mitchellh): Properly handle multiple raw configs. This isn't
|
||||
// very pressing at the moment because at the time of this comment
|
||||
// only the first member of raws can contain the actual type-overrides.
|
||||
var mapConfig map[string]interface{}
|
||||
if err := mapstructure.Decode(raws[0], &mapConfig); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Failed to decode config: %s", err))
|
||||
return errs
|
||||
}
|
||||
|
||||
p.premade = make(map[string]packer.PostProcessor)
|
||||
for k, raw := range mapConfig {
|
||||
pp, err := p.subPostProcessor(k, raw, p.extraConfig)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
continue
|
||||
config := new(Config)
|
||||
p.configs[name] = config
|
||||
if err := p.configureSingle(config, subRaws...); err != nil {
|
||||
return fmt.Errorf("Error configuring %s: %s", name, err)
|
||||
}
|
||||
|
||||
if pp == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
p.premade[k] = pp
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
ppName, ok := builtins[artifact.BuilderId()]
|
||||
name, ok := builtins[artifact.BuilderId()]
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId())
|
||||
return nil, false, fmt.Errorf(
|
||||
"Unknown artifact type, can't build box: %s", artifact.BuilderId())
|
||||
}
|
||||
|
||||
// Use the premade PostProcessor if we have one. Otherwise, we
|
||||
// create it and configure it here.
|
||||
pp, ok := p.premade[ppName]
|
||||
if !ok {
|
||||
log.Printf("Premade post-processor for '%s' not found. Creating.", ppName)
|
||||
provider := providerForName(name)
|
||||
if provider == nil {
|
||||
// This shouldn't happen since we hard code all of these ourselves
|
||||
panic(fmt.Sprintf("bad provider name: %s", name))
|
||||
}
|
||||
|
||||
var err error
|
||||
pp, err = p.subPostProcessor(ppName, nil, p.extraConfig)
|
||||
config := p.configs[""]
|
||||
if specificConfig, ok := p.configs[name]; ok {
|
||||
config = specificConfig
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", name))
|
||||
|
||||
outputPath, err := config.tpl.Process(config.OutputPath, &outputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: config.PackerBuildName,
|
||||
Provider: name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create a temporary directory for us to build the contents of the box in
|
||||
dir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Copy all of the includes files into the temporary directory
|
||||
for _, src := range config.Include {
|
||||
ui.Message(fmt.Sprintf("Copying from include: %s", src))
|
||||
dst := filepath.Join(dir, filepath.Base(src))
|
||||
if err := CopyContents(dst, src); err != nil {
|
||||
err = fmt.Errorf("Error copying include file: %s\n\n%s", src, err)
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Run the provider processing step
|
||||
vagrantfile, metadata, err := provider.Process(ui, artifact, dir)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Write the metadata we got
|
||||
if err := WriteMetadata(dir, metadata); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Write our Vagrantfile
|
||||
var customVagrantfile string
|
||||
if config.VagrantfileTemplate != "" {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Using custom Vagrantfile: %s", config.VagrantfileTemplate))
|
||||
customBytes, err := ioutil.ReadFile(config.VagrantfileTemplate)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if pp == nil {
|
||||
return nil, false, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
|
||||
customVagrantfile = string(customBytes)
|
||||
}
|
||||
|
||||
f, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
t := template.Must(template.New("root").Parse(boxVagrantfileContents))
|
||||
err = t.Execute(f, &vagrantfileTemplate{
|
||||
ProviderVagrantfile: vagrantfile,
|
||||
CustomVagrantfile: customVagrantfile,
|
||||
})
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create the box
|
||||
if err := DirToBox(outputPath, dir, ui, config.CompressionLevel); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (p *PostProcessor) configureSingle(config *Config, raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.tpl.UserVars = config.PackerUserVars
|
||||
|
||||
// Defaults
|
||||
if config.OutputPath == "" {
|
||||
config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box"
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, k := range md.Keys {
|
||||
if k == "compression_level" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", ppName))
|
||||
return pp.PostProcess(ui, artifact)
|
||||
}
|
||||
|
||||
func (p *PostProcessor) subPostProcessor(key string, specific interface{}, extra map[string]interface{}) (packer.PostProcessor, error) {
|
||||
pp := keyToPostProcessor(key)
|
||||
if pp == nil {
|
||||
return nil, nil
|
||||
if !found {
|
||||
config.CompressionLevel = flate.DefaultCompression
|
||||
}
|
||||
|
||||
if err := pp.Configure(extra, specific); err != nil {
|
||||
return nil, err
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
validates := map[string]*string{
|
||||
"output": &config.OutputPath,
|
||||
"vagrantfile_template": &config.VagrantfileTemplate,
|
||||
}
|
||||
|
||||
return pp, nil
|
||||
for n, ptr := range validates {
|
||||
if err := config.tpl.Validate(*ptr); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyToPostProcessor maps a configuration key to the actual post-processor
|
||||
// it will be configuring. This returns a new instance of that post-processor.
|
||||
func keyToPostProcessor(key string) packer.PostProcessor {
|
||||
switch key {
|
||||
case "aws":
|
||||
return new(AWSBoxPostProcessor)
|
||||
case "digitalocean":
|
||||
return new(DigitalOceanBoxPostProcessor)
|
||||
func providerForName(name string) Provider {
|
||||
switch name {
|
||||
case "virtualbox":
|
||||
return new(VBoxBoxPostProcessor)
|
||||
case "vmware":
|
||||
return new(VMwareBoxPostProcessor)
|
||||
return new(VBoxProvider)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OutputPathTemplate is the structure that is availalable within the
|
||||
// OutputPath variables.
|
||||
type outputPathTemplate struct {
|
||||
ArtifactId string
|
||||
BuildName string
|
||||
Provider string
|
||||
}
|
||||
|
||||
type vagrantfileTemplate struct {
|
||||
ProviderVagrantfile string
|
||||
CustomVagrantfile string
|
||||
}
|
||||
|
||||
const boxVagrantfileContents string = `
|
||||
# The contents below were provided by the Packer Vagrant post-processor
|
||||
{{ .ProviderVagrantfile }}
|
||||
|
||||
# The contents below (if any) are custom contents provided by the
|
||||
# Packer template during image build.
|
||||
{{ .CustomVagrantfile }}
|
||||
`
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -9,15 +12,55 @@ func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &PostProcessor{}
|
||||
if _, ok := raw.(packer.PostProcessor); !ok {
|
||||
t.Fatalf("AWS PostProcessor should be a PostProcessor")
|
||||
func testPP(t *testing.T) *PostProcessor {
|
||||
var p PostProcessor
|
||||
if err := p.Configure(testConfig()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func testUi() *packer.BasicUi {
|
||||
return &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_OutputPath(t *testing.T) {
|
||||
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var _ packer.PostProcessor = new(PostProcessor)
|
||||
}
|
||||
|
||||
func TestPostProcessorPrepare_compressionLevel(t *testing.T) {
|
||||
var p PostProcessor
|
||||
|
||||
// Default
|
||||
c := testConfig()
|
||||
delete(c, "compression_level")
|
||||
if err := p.Configure(c); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config := p.configs[""]
|
||||
if config.CompressionLevel != flate.DefaultCompression {
|
||||
t.Fatalf("bad: %#v", config.CompressionLevel)
|
||||
}
|
||||
|
||||
// Set
|
||||
c = testConfig()
|
||||
c["compression_level"] = 7
|
||||
if err := p.Configure(c); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config = p.configs[""]
|
||||
if config.CompressionLevel != 7 {
|
||||
t.Fatalf("bad: %#v", config.CompressionLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessorPrepare_outputPath(t *testing.T) {
|
||||
var p PostProcessor
|
||||
|
||||
// Default
|
||||
@@ -36,14 +79,57 @@ func TestBuilderPrepare_OutputPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_PPConfig(t *testing.T) {
|
||||
func TestPostProcessorPrepare_subConfigs(t *testing.T) {
|
||||
var p PostProcessor
|
||||
|
||||
// Default
|
||||
c := testConfig()
|
||||
c["aws"] = map[string]interface{}{}
|
||||
c["compression_level"] = 42
|
||||
c["vagrantfile_template"] = "foo"
|
||||
c["override"] = map[string]interface{}{
|
||||
"aws": map[string]interface{}{
|
||||
"compression_level": 7,
|
||||
},
|
||||
}
|
||||
err := p.Configure(c)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.configs[""].CompressionLevel != 42 {
|
||||
t.Fatalf("bad: %#v", p.configs[""].CompressionLevel)
|
||||
}
|
||||
|
||||
if p.configs[""].VagrantfileTemplate != "foo" {
|
||||
t.Fatalf("bad: %#v", p.configs[""].VagrantfileTemplate)
|
||||
}
|
||||
|
||||
if p.configs["aws"].CompressionLevel != 7 {
|
||||
t.Fatalf("bad: %#v", p.configs["aws"].CompressionLevel)
|
||||
}
|
||||
|
||||
if p.configs["aws"].VagrantfileTemplate != "foo" {
|
||||
t.Fatalf("bad: %#v", p.configs["aws"].VagrantfileTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessorPostProcess_badId(t *testing.T) {
|
||||
artifact := &packer.MockArtifact{
|
||||
BuilderIdValue: "invalid.packer",
|
||||
}
|
||||
|
||||
_, _, err := testPP(t).PostProcess(testUi(), artifact)
|
||||
if !strings.Contains(err.Error(), "artifact type") {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderForName(t *testing.T) {
|
||||
if v, ok := providerForName("virtualbox").(*VBoxProvider); !ok {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
|
||||
if providerForName("nope") != nil {
|
||||
t.Fatal("should be nil if bad provider")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// Provider is the interface that each provider must implement in order
|
||||
// to package the artifacts into a Vagrant-compatible box.
|
||||
type Provider interface {
|
||||
// Process is called to process an artifact into a Vagrant box. The
|
||||
// artifact is given as well as the temporary directory path to
|
||||
// put things.
|
||||
//
|
||||
// The Provider should return the contents for the Vagrantfile,
|
||||
// any metadata (including the provider type in that), and an error
|
||||
// if any.
|
||||
Process(packer.Ui, packer.Artifact, string) (vagrantfile string, metadata map[string]interface{}, err error)
|
||||
}
|
||||
@@ -13,14 +13,6 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// OutputPathTemplate is the structure that is availalable within the
|
||||
// OutputPath variables.
|
||||
type OutputPathTemplate struct {
|
||||
ArtifactId string
|
||||
BuildName string
|
||||
Provider string
|
||||
}
|
||||
|
||||
// Copies a file by copying the contents of the file to another place.
|
||||
func CopyContents(dst, src string) error {
|
||||
srcF, err := os.Open(src)
|
||||
|
||||
@@ -2,10 +2,8 @@ package vagrant
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/flate"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -15,183 +13,49 @@ import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type VBoxBoxConfig struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
type VBoxProvider struct{}
|
||||
|
||||
Include []string `mapstructure:"include"`
|
||||
OutputPath string `mapstructure:"output"`
|
||||
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
|
||||
CompressionLevel int `mapstructure:"compression_level"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type VBoxVagrantfileTemplate struct {
|
||||
BaseMacAddress string
|
||||
}
|
||||
|
||||
type VBoxBoxPostProcessor struct {
|
||||
config VBoxBoxConfig
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.config.tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Defaults
|
||||
found := false
|
||||
for _, k := range md.Keys {
|
||||
println(k)
|
||||
if k == "compression_level" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
p.config.CompressionLevel = flate.DefaultCompression
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
validates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
"vagrantfile_template": &p.config.VagrantfileTemplate,
|
||||
}
|
||||
|
||||
for n, ptr := range validates {
|
||||
if err := p.config.tpl.Validate(*ptr); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
var err error
|
||||
|
||||
// Compile the output path
|
||||
outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: p.config.PackerBuildName,
|
||||
Provider: "virtualbox",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create a temporary directory for us to build the contents of the box in
|
||||
dir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Copy all of the includes files into the temporary directory
|
||||
for _, src := range p.config.Include {
|
||||
ui.Message(fmt.Sprintf("Copying from include: %s", src))
|
||||
dst := filepath.Join(dir, filepath.Base(src))
|
||||
if err := CopyContents(dst, src); err != nil {
|
||||
err = fmt.Errorf("Error copying include file: %s\n\n%s", src, err)
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
func (p *VBoxProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "virtualbox"}
|
||||
|
||||
// Copy all of the original contents into the temporary directory
|
||||
for _, path := range artifact.Files() {
|
||||
|
||||
// We treat OVA files specially, we unpack those into the temporary
|
||||
// directory so we can get the resulting disk and OVF.
|
||||
if extension := filepath.Ext(path); extension == ".ova" {
|
||||
ui.Message(fmt.Sprintf("Unpacking OVA: %s", path))
|
||||
if err := DecompressOva(dir, path); err != nil {
|
||||
return nil, false, err
|
||||
if err = DecompressOva(dir, path); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ui.Message(fmt.Sprintf("Copying from artifact: %s", path))
|
||||
dstPath := filepath.Join(dir, filepath.Base(path))
|
||||
if err := CopyContents(dstPath, path); err != nil {
|
||||
return nil, false, err
|
||||
if err = CopyContents(dstPath, path); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create the Vagrantfile from the template
|
||||
tplData := &VBoxVagrantfileTemplate{}
|
||||
tplData.BaseMacAddress, err = p.findBaseMacAddress(dir)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer vf.Close()
|
||||
|
||||
vagrantfileContents := defaultVBoxVagrantfile
|
||||
if p.config.VagrantfileTemplate != "" {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Using Vagrantfile template: %s", p.config.VagrantfileTemplate))
|
||||
f, err := os.Open(p.config.VagrantfileTemplate)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
vagrantfileContents = string(contents)
|
||||
}
|
||||
|
||||
vagrantfileContents, err = p.config.tpl.Process(vagrantfileContents, tplData)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Error writing Vagrantfile: %s", err)
|
||||
}
|
||||
vf.Write([]byte(vagrantfileContents))
|
||||
vf.Close()
|
||||
|
||||
// Create the metadata
|
||||
metadata := map[string]string{"provider": "virtualbox"}
|
||||
if err := WriteMetadata(dir, metadata); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Rename the OVF file to box.ovf, as required by Vagrant
|
||||
ui.Message("Renaming the OVF to box.ovf...")
|
||||
if err := p.renameOVF(dir); err != nil {
|
||||
return nil, false, err
|
||||
if err = p.renameOVF(dir); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Compress the directory to the given output path
|
||||
ui.Message(fmt.Sprintf("Compressing box..."))
|
||||
if err := DirToBox(outputPath, dir, ui, p.config.CompressionLevel); err != nil {
|
||||
return nil, false, err
|
||||
// Create the Vagrantfile from the template
|
||||
var baseMacAddress string
|
||||
baseMacAddress, err = p.findBaseMacAddress(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return NewArtifact("virtualbox", outputPath), false, nil
|
||||
vagrantfile = fmt.Sprintf(vboxVagrantfile, baseMacAddress)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) findOvf(dir string) (string, error) {
|
||||
func (p *VBoxProvider) findOvf(dir string) (string, error) {
|
||||
log.Println("Looking for OVF in artifact...")
|
||||
file_matches, err := filepath.Glob(filepath.Join(dir, "*.ovf"))
|
||||
if err != nil {
|
||||
@@ -209,7 +73,7 @@ func (p *VBoxBoxPostProcessor) findOvf(dir string) (string, error) {
|
||||
return file_matches[0], err
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
|
||||
func (p *VBoxProvider) renameOVF(dir string) error {
|
||||
log.Println("Looking for OVF to rename...")
|
||||
ovf, err := p.findOvf(dir)
|
||||
if err != nil {
|
||||
@@ -220,7 +84,7 @@ func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
|
||||
return os.Rename(ovf, filepath.Join(dir, "box.ovf"))
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) findBaseMacAddress(dir string) (string, error) {
|
||||
func (p *VBoxProvider) findBaseMacAddress(dir string) (string, error) {
|
||||
log.Println("Looking for OVF for base mac address...")
|
||||
ovf, err := p.findOvf(dir)
|
||||
if err != nil {
|
||||
@@ -295,8 +159,8 @@ func DecompressOva(dir, src string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var defaultVBoxVagrantfile = `
|
||||
var vboxVagrantfile = `
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.base_mac = "{{ .BaseMacAddress }}"
|
||||
config.vm.base_mac = "%s"
|
||||
end
|
||||
`
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVBoxBoxPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &VBoxBoxPostProcessor{}
|
||||
if _, ok := raw.(packer.PostProcessor); !ok {
|
||||
t.Fatalf("VBox PostProcessor should be a PostProcessor")
|
||||
}
|
||||
func TestVBoxProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(VBoxProvider)
|
||||
}
|
||||
|
||||
@@ -1,139 +1,26 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type VMwareBoxConfig struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
type VMwareProvider struct{}
|
||||
|
||||
OutputPath string `mapstructure:"output"`
|
||||
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
|
||||
CompressionLevel string `mapstructure:"compression_level"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type VMwareBoxPostProcessor struct {
|
||||
config VMwareBoxConfig
|
||||
}
|
||||
|
||||
func (p *VMwareBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.config.tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
validates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
"vagrantfile_template": &p.config.VagrantfileTemplate,
|
||||
"compression_level": &p.config.CompressionLevel,
|
||||
}
|
||||
|
||||
for n, ptr := range validates {
|
||||
if err := p.config.tpl.Validate(*ptr); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
// Compile the output path
|
||||
outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: p.config.PackerBuildName,
|
||||
Provider: "vmware",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create a temporary directory for us to build the contents of the box in
|
||||
dir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
func (p *VMwareProvider) Process(ui packer.Ui, artifact packer.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
|
||||
// Create the metadata
|
||||
metadata = map[string]interface{}{"provider": "vmware_desktop"}
|
||||
|
||||
// Copy all of the original contents into the temporary directory
|
||||
for _, path := range artifact.Files() {
|
||||
ui.Message(fmt.Sprintf("Copying: %s", path))
|
||||
|
||||
dstPath := filepath.Join(dir, filepath.Base(path))
|
||||
if err := CopyContents(dstPath, path); err != nil {
|
||||
return nil, false, err
|
||||
if err = CopyContents(dstPath, path); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.VagrantfileTemplate != "" {
|
||||
f, err := os.Open(p.config.VagrantfileTemplate)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create the Vagrantfile from the template
|
||||
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer vf.Close()
|
||||
|
||||
vagrantfileContents, err := p.config.tpl.Process(string(contents), nil)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Error writing Vagrantfile: %s", err)
|
||||
}
|
||||
vf.Write([]byte(vagrantfileContents))
|
||||
vf.Close()
|
||||
}
|
||||
|
||||
var level int = flate.DefaultCompression
|
||||
if p.config.CompressionLevel != "" {
|
||||
level, err = strconv.Atoi(p.config.CompressionLevel)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create the metadata
|
||||
metadata := map[string]string{"provider": "vmware_desktop"}
|
||||
if err := WriteMetadata(dir, metadata); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Compress the directory to the given output path
|
||||
ui.Message(fmt.Sprintf("Compressing box..."))
|
||||
if err := DirToBox(outputPath, dir, ui, level); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return NewArtifact("vmware", outputPath), false, nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVMwareBoxPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &VMwareBoxPostProcessor{}
|
||||
if _, ok := raw.(packer.PostProcessor); !ok {
|
||||
t.Fatalf("VMware PostProcessor should be a PostProcessor")
|
||||
}
|
||||
func TestVMwareProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(VMwareProvider)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ documentation on [using post-processors](/docs/templates/post-processors.html)
|
||||
in templates. This knowledge will be expected for the remainder of
|
||||
this document.
|
||||
|
||||
Because Vagrant boxes are [provider-specific](#),
|
||||
Because Vagrant boxes are [provider-specific](http://docs.vagrantup.com/v2/boxes/format.html),
|
||||
the Vagrant post-processor is hardcoded to understand how to convert
|
||||
the artifacts of certain builders into proper boxes for their
|
||||
respective providers.
|
||||
@@ -27,6 +27,7 @@ Currently, the Vagrant post-processor can create boxes for the following
|
||||
providers.
|
||||
|
||||
* AWS
|
||||
* DigitalOcean
|
||||
* VirtualBox
|
||||
* VMware
|
||||
|
||||
@@ -47,82 +48,52 @@ However, if you want to configure things a bit more, the post-processor
|
||||
does expose some configuration options. The available options are listed
|
||||
below, with more details about certain options in following sections.
|
||||
|
||||
* `compression_level` (integer) - An integer repesenting the
|
||||
compression level to use when creating the Vagrant box. Valid
|
||||
values range from 0 to 9, with 0 being no compression and 9 being
|
||||
the best compression. By default, compression is enabled at level 1.
|
||||
|
||||
* `include` (array of strings) - Paths to files to include in the
|
||||
Vagrant box. These files will each be copied into the top level directory
|
||||
of the Vagrant box (regardless of their paths). They can then be used
|
||||
from the Vagrantfile.
|
||||
|
||||
* `output` (string) - The full path to the box file that will be created
|
||||
by this post-processor. This is a
|
||||
[configuration template](/docs/templates/configuration-templates.html).
|
||||
The variable `Provider` is replaced by the Vagrant provider the box is for.
|
||||
The variable `ArtifactId` is replaced by the ID of the input artifact.
|
||||
The variable `BuildName` is replaced with the name of the build.
|
||||
By default, the value of this config is `packer_{{.BuildName}}_{{.Provider}}.box`.
|
||||
|
||||
* `aws`, `virtualbox`, or `vmware` (objects) - These are used to configure
|
||||
the specific options for certain providers. A reference of available
|
||||
configuration parameters for each is in the section below.
|
||||
|
||||
### AWS Provider
|
||||
|
||||
The AWS provider itself can be configured with specific options:
|
||||
|
||||
* `vagrantfile_template` (string) - Path to a template to use for the
|
||||
Vagrantfile that is packaged with the box. The contents of the file must be a valid Go
|
||||
[text template](http://golang.org/pkg/text/template). By default
|
||||
this is a template that simply sets the AMIs for the various regions
|
||||
of the AWS build.
|
||||
Vagrantfile that is packaged with the box.
|
||||
|
||||
* `compression_level` (integer) - An integer repesenting the
|
||||
compression level to use when creating the Vagrant box. Valid
|
||||
values range from 0 to 9, with 0 being no compression and 9 being
|
||||
the best compression.
|
||||
## Provider-Specific Overrides
|
||||
|
||||
The `vagrantfile_template` has the `Images` variable which is a map
|
||||
of region (string) to AMI ID (string). An example Vagrantfile template for
|
||||
AWS is shown below. The example simply sets the AMI for each region.
|
||||
If you have a Packer template with multiple builder types within it,
|
||||
you may want to configure the box creation for each type a little differently.
|
||||
For example, the contents of the Vagrantfile for a Vagrant box for AWS might
|
||||
be different from the contents of the Vagrantfile you want for VMware.
|
||||
The post-processor lets you do this.
|
||||
|
||||
```
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.provider "aws" do |aws|
|
||||
{{ range $region, $ami := .Images }}
|
||||
aws.region_config "{{ $region }}", ami: "{{ $ami }}"
|
||||
{{ end }}
|
||||
end
|
||||
end
|
||||
Specify overrides within the `override` configuration by provider name:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vagrant",
|
||||
|
||||
"compression_level": 1,
|
||||
"override": {
|
||||
"vmware": {
|
||||
"compression_level": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### VirtualBox Provider
|
||||
In the example above, the compression level will be set to 1 except for
|
||||
VMware, where it will be set to 0.
|
||||
|
||||
The VirtualBox provider itself can be configured with specific options:
|
||||
|
||||
* `vagrantfile_template` (string) - Path to a template to use for the
|
||||
Vagrantfile that is packaged with the box. The contents of the file must be a valid Go
|
||||
[text template](http://golang.org/pkg/text/template). By default this is
|
||||
a template that just sets the base MAC address so that networking works.
|
||||
|
||||
* `compression_level` (integer) - An integer repesenting the
|
||||
compression level to use when creating the Vagrant box. Valid
|
||||
values range from 0 to 9, with 0 being no compression and 9 being
|
||||
the best compression.
|
||||
|
||||
The `vagrantfile_template` has the `BaseMACAddress` variable which is a string
|
||||
containing the MAC address of the first network interface. This must be set
|
||||
in the Vagrantfile for networking to work properly with Vagrant. An example
|
||||
Vagrantfile template is shown below:
|
||||
|
||||
```
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.base_mac = "{{ .BaseMacAddress }}"
|
||||
end
|
||||
```
|
||||
|
||||
### VMware Provider
|
||||
|
||||
The VMware provider itself can be configured with specific options:
|
||||
|
||||
* `vagrantfile_template` (string) - Path to a template to use for the
|
||||
Vagrantfile that is packaged with the box. The contents of the file must be a valid Go
|
||||
[text template](http://golang.org/pkg/text/template). By default no
|
||||
Vagrantfile is packaged with the box. Note that currently no variables
|
||||
are available in the template, but this may change in the future.
|
||||
|
||||
* `compression_level` (integer) - An integer repesenting the
|
||||
compression level to use when creating the Vagrant box. Valid
|
||||
values range from 0 to 9, with 0 being no compression and 9 being
|
||||
the best compression.
|
||||
The available provider names are: `aws`, `digitalocean`, `virtualbox`,
|
||||
and `vmware`.
|
||||
|
||||
Reference in New Issue
Block a user