From 3dd4c08f2d95b2afdaa998b267eee5780e0bc15e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 13:22:46 -0800 Subject: [PATCH 01/11] post-processor/vagrant: start new format --- post-processor/vagrant/post-processor.go | 211 +++++++++++------- post-processor/vagrant/post-processor_test.go | 20 +- post-processor/vagrant/provider.go | 18 ++ post-processor/vagrant/util.go | 8 - post-processor/vagrant/virtualbox.go | 180 ++------------- post-processor/vagrant/virtualbox_test.go | 9 +- 6 files changed, 182 insertions(+), 264 deletions(-) create mode 100644 post-processor/vagrant/provider.go diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 75d99215a..57ef84b92 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -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,74 +26,76 @@ var builtins = map[string]string{ type Config struct { common.PackerConfig `mapstructure:",squash"` - OutputPath string `mapstructure:"output"` + Include []string `mapstructure:"include"` + OutputPath string `mapstructure:"output"` + VagrantfileTemplate string `mapstructure:"vagrantfile_template"` + CompressionLevel int `mapstructure:"compression_level"` + + tpl *packer.ConfigTemplate +} + +// OutputPathTemplate is the structure that is availalable within the +// OutputPath variables. +type OutputPathTemplate struct { + ArtifactId string + BuildName string + Provider string } type PostProcessor struct { - config Config - premade map[string]packer.PostProcessor - extraConfig map[string]interface{} + config Config +} + +type VagrantfileTemplate struct { + ProviderVagrantfile string + CustomVagrantfile string } func (p *PostProcessor) Configure(raws ...interface{}) error { - _, err := common.DecodeConfig(&p.config, raws...) + md, err := common.DecodeConfig(&p.config, raws...) if err != nil { return err } - tpl, err := packer.NewConfigTemplate() + p.config.tpl, err = packer.NewConfigTemplate() if err != nil { return err } - tpl.UserVars = p.config.PackerUserVars + p.config.tpl.UserVars = p.config.PackerUserVars // 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 + found := false + for _, k := range md.Keys { + if k == "compression_level" { + found = true + break + } } - 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 - } + if !found { + p.config.CompressionLevel = flate.DefaultCompression + } - if pp == nil { - continue - } + // Accumulate any errors + errs := common.CheckUnusedConfig(md) + + validates := map[string]*string{ + "output": &p.config.OutputPath, + "vagrantfile_template": &p.config.VagrantfileTemplate, + } - p.premade[k] = pp + 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 len(errs.Errors) > 0 { + if errs != nil && len(errs.Errors) > 0 { return errs } @@ -97,58 +103,107 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } 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) - if err != nil { + ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", name)) + + outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{ + ArtifactId: artifact.Id(), + BuildName: p.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 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 } + } + + // 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 + } - if pp == nil { - return nil, false, fmt.Errorf("Vagrant box post-processor not found: %s", ppName) + // Write our Vagrantfile + var customVagrantfile string + if p.config.VagrantfileTemplate != "" { + ui.Message(fmt.Sprintf( + "Using custom Vagrantfile: %s", p.config.VagrantfileTemplate)) + customBytes, err := ioutil.ReadFile(p.config.VagrantfileTemplate) + if err != nil { + return nil, false, err } + + customVagrantfile = string(customBytes) } - ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", ppName)) - return pp.PostProcess(ui, artifact) -} + f, err := os.Create(filepath.Join(dir, "Vagrantfile")) + if err != nil { + return nil, false, err + } -func (p *PostProcessor) subPostProcessor(key string, specific interface{}, extra map[string]interface{}) (packer.PostProcessor, error) { - pp := keyToPostProcessor(key) - if pp == nil { - return nil, nil + 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 } - if err := pp.Configure(extra, specific); err != nil { - return nil, err + // Create the box + if err := DirToBox(outputPath, dir, ui, p.config.CompressionLevel); err != nil { + return nil, false, err } - return pp, nil + return nil, false, 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 } } + +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 }} +` diff --git a/post-processor/vagrant/post-processor_test.go b/post-processor/vagrant/post-processor_test.go index 9a0ac876b..9046b0cf0 100644 --- a/post-processor/vagrant/post-processor_test.go +++ b/post-processor/vagrant/post-processor_test.go @@ -10,11 +10,7 @@ func testConfig() 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") - } + var _ packer.PostProcessor = new(PostProcessor) } func TestBuilderPrepare_OutputPath(t *testing.T) { @@ -36,14 +32,12 @@ func TestBuilderPrepare_OutputPath(t *testing.T) { } } -func TestBuilderPrepare_PPConfig(t *testing.T) { - var p PostProcessor +func TestProviderForName(t *testing.T) { + if v, ok := providerForName("virtualbox").(*VBoxProvider); !ok { + t.Fatalf("bad: %#v", v) + } - // Default - c := testConfig() - c["aws"] = map[string]interface{}{} - err := p.Configure(c) - if err != nil { - t.Fatalf("err: %s", err) + if providerForName("nope") != nil { + t.Fatal("should be nil if bad provider") } } diff --git a/post-processor/vagrant/provider.go b/post-processor/vagrant/provider.go new file mode 100644 index 000000000..00201cb9c --- /dev/null +++ b/post-processor/vagrant/provider.go @@ -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) +} diff --git a/post-processor/vagrant/util.go b/post-processor/vagrant/util.go index e8be84e43..d6a1a750a 100644 --- a/post-processor/vagrant/util.go +++ b/post-processor/vagrant/util.go @@ -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) diff --git a/post-processor/vagrant/virtualbox.go b/post-processor/vagrant/virtualbox.go index 2711c9159..eeaf401c6 100644 --- a/post-processor/vagrant/virtualbox.go +++ b/post-processor/vagrant/virtualbox.go @@ -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 ` diff --git a/post-processor/vagrant/virtualbox_test.go b/post-processor/vagrant/virtualbox_test.go index eb525c29b..c3815ce77 100644 --- a/post-processor/vagrant/virtualbox_test.go +++ b/post-processor/vagrant/virtualbox_test.go @@ -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) } From c1a9728448c8df63c05a58f05fd2270535288be0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 13:37:09 -0800 Subject: [PATCH 02/11] post-processor/vagrant: transition aws over --- post-processor/vagrant/aws.go | 151 ++++------------------------- post-processor/vagrant/aws_test.go | 9 +- 2 files changed, 21 insertions(+), 139 deletions(-) diff --git a/post-processor/vagrant/aws.go b/post-processor/vagrant/aws.go index 04ef43ec0..04e074bc5 100644 --- a/post-processor/vagrant/aws.go +++ b/post-processor/vagrant/aws.go @@ -1,157 +1,44 @@ 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 AWSBoxConfig struct { - common.PackerConfig `mapstructure:",squash"` +type AWSProvider struct{} - OutputPath string `mapstructure:"output"` - VagrantfileTemplate string `mapstructure:"vagrantfile_template"` - CompressionLevel string `mapstructure:"compression_level"` - - 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 *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"} -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 - } - - // 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 - } + // 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 +} - return NewArtifact("aws", outputPath), true, nil +type awsVagrantfileTemplate struct { + Images map[string]string } var defaultAWSVagrantfile = ` diff --git a/post-processor/vagrant/aws_test.go b/post-processor/vagrant/aws_test.go index 7c7d6fcc3..f685e030e 100644 --- a/post-processor/vagrant/aws_test.go +++ b/post-processor/vagrant/aws_test.go @@ -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) } From 23e73b12264035dc37412c456d79328bc207f60e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 13:41:48 -0800 Subject: [PATCH 03/11] post-processor/vagrant: DigitalOcean --- post-processor/vagrant/aws.go | 3 +- post-processor/vagrant/digitalocean.go | 147 +++----------------- post-processor/vagrant/digitalocean_test.go | 9 +- 3 files changed, 20 insertions(+), 139 deletions(-) diff --git a/post-processor/vagrant/aws.go b/post-processor/vagrant/aws.go index 04e074bc5..cddadd7e6 100644 --- a/post-processor/vagrant/aws.go +++ b/post-processor/vagrant/aws.go @@ -3,9 +3,10 @@ package vagrant import ( "bytes" "fmt" - "github.com/mitchellh/packer/packer" "strings" "text/template" + + "github.com/mitchellh/packer/packer" ) type AWSProvider struct{} diff --git a/post-processor/vagrant/digitalocean.go b/post-processor/vagrant/digitalocean.go index 3ff83d04e..aa7a757ff 100644 --- a/post-processor/vagrant/digitalocean.go +++ b/post-processor/vagrant/digitalocean.go @@ -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 -} - -func (p *DigitalOceanBoxPostProcessor) Configure(rDigitalOcean ...interface{}) error { - md, err := common.DecodeConfig(&p.config, rDigitalOcean...) - 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) +type DigitalOceanProvider struct{} - 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 *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"} -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 - ` diff --git a/post-processor/vagrant/digitalocean_test.go b/post-processor/vagrant/digitalocean_test.go index 44451c6a4..09ca6fdf4 100644 --- a/post-processor/vagrant/digitalocean_test.go +++ b/post-processor/vagrant/digitalocean_test.go @@ -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) } From 2f09eb5bba01d35f171eab42557cdbcf2733cef1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 13:44:18 -0800 Subject: [PATCH 04/11] post-processor/vagrant: vmware --- post-processor/vagrant/vmware.go | 127 ++------------------------ post-processor/vagrant/vmware_test.go | 9 +- 2 files changed, 9 insertions(+), 127 deletions(-) diff --git a/post-processor/vagrant/vmware.go b/post-processor/vagrant/vmware.go index b12a2d2f6..d3ff50d24 100644 --- a/post-processor/vagrant/vmware.go +++ b/post-processor/vagrant/vmware.go @@ -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 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 + if err = CopyContents(dstPath, path); err != nil { + return } - 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 } diff --git a/post-processor/vagrant/vmware_test.go b/post-processor/vagrant/vmware_test.go index ca3cbe7cd..e47dd3080 100644 --- a/post-processor/vagrant/vmware_test.go +++ b/post-processor/vagrant/vmware_test.go @@ -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) } From 7b6bbbf42f7b2d20961ad3ebce9c18faebac1a70 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 13:52:48 -0800 Subject: [PATCH 05/11] post-procssor/vagrant: don't export some things --- post-processor/vagrant/post-processor.go | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 57ef84b92..7772d69c0 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -34,23 +34,10 @@ type Config struct { tpl *packer.ConfigTemplate } -// OutputPathTemplate is the structure that is availalable within the -// OutputPath variables. -type OutputPathTemplate struct { - ArtifactId string - BuildName string - Provider string -} - type PostProcessor struct { config Config } -type VagrantfileTemplate struct { - ProviderVagrantfile string - CustomVagrantfile string -} - func (p *PostProcessor) Configure(raws ...interface{}) error { md, err := common.DecodeConfig(&p.config, raws...) if err != nil { @@ -117,7 +104,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", name)) - outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{ + outputPath, err := p.config.tpl.Process(p.config.OutputPath, &outputPathTemplate{ ArtifactId: artifact.Id(), BuildName: p.config.PackerBuildName, Provider: name, @@ -173,7 +160,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac } t := template.Must(template.New("root").Parse(boxVagrantfileContents)) - err = t.Execute(f, &VagrantfileTemplate{ + err = t.Execute(f, &vagrantfileTemplate{ ProviderVagrantfile: vagrantfile, CustomVagrantfile: customVagrantfile, }) @@ -199,6 +186,19 @@ func providerForName(name string) Provider { } } +// 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 }} From 6518c92e25f1c2893b8cca9a70b9d05dbe2e7ca9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 14:04:45 -0800 Subject: [PATCH 06/11] post-processor/vagrant: more tests --- packer/artifact_mock.go | 22 +++++-- post-processor/vagrant/post-processor_test.go | 58 ++++++++++++++++++- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/packer/artifact_mock.go b/packer/artifact_mock.go index ec88007f3..a59af5331 100644 --- a/packer/artifact_mock.go +++ b/packer/artifact_mock.go @@ -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 { diff --git a/post-processor/vagrant/post-processor_test.go b/post-processor/vagrant/post-processor_test.go index 9046b0cf0..552c7ba02 100644 --- a/post-processor/vagrant/post-processor_test.go +++ b/post-processor/vagrant/post-processor_test.go @@ -1,7 +1,10 @@ package vagrant import ( + "bytes" + "compress/flate" "github.com/mitchellh/packer/packer" + "strings" "testing" ) @@ -9,11 +12,53 @@ func testConfig() map[string]interface{} { return map[string]interface{}{} } +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 TestPostProcessor_ImplementsPostProcessor(t *testing.T) { var _ packer.PostProcessor = new(PostProcessor) } -func TestBuilderPrepare_OutputPath(t *testing.T) { +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) + } + + if p.config.CompressionLevel != flate.DefaultCompression { + t.Fatalf("bad: %#v", p.config.CompressionLevel) + } + + // Set + c = testConfig() + c["compression_level"] = 7 + if err := p.Configure(c); err != nil { + t.Fatalf("err: %s", err) + } + + if p.config.CompressionLevel != 7 { + t.Fatalf("bad: %#v", p.config.CompressionLevel) + } +} + +func TestPostProcessorPrepare_outputPath(t *testing.T) { var p PostProcessor // Default @@ -32,6 +77,17 @@ func TestBuilderPrepare_OutputPath(t *testing.T) { } } +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) From b6795081b81dab75bf9008a5e1180a19a61f552f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 14:10:28 -0800 Subject: [PATCH 07/11] website: update docs for vagrant pp --- .../post-processors/vagrant.html.markdown | 86 +++---------------- 1 file changed, 14 insertions(+), 72 deletions(-) diff --git a/website/source/docs/post-processors/vagrant.html.markdown b/website/source/docs/post-processors/vagrant.html.markdown index b91b16970..2b5f4368f 100644 --- a/website/source/docs/post-processors/vagrant.html.markdown +++ b/website/source/docs/post-processors/vagrant.html.markdown @@ -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,23 @@ 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. - -* `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 `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. - -``` -Vagrant.configure("2") do |config| - config.vm.provider "aws" do |aws| - {{ range $region, $ami := .Images }} - aws.region_config "{{ $region }}", ami: "{{ $ami }}" - {{ end }} - end -end -``` - -### VirtualBox Provider - -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. + Vagrantfile that is packaged with the box. From 84f8c0bfa0938c32cbb63029bfbfdd3acf6d99f6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 14:44:12 -0800 Subject: [PATCH 08/11] command/fix: cleaner --- command/fix/command.go | 9 +-------- command/fix/fixer.go | 9 +++++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/command/fix/command.go b/command/fix/command.go index 5de003cea..c62d3676b 100644 --- a/command/fix/command.go +++ b/command/fix/command.go @@ -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 { diff --git a/command/fix/fixer.go b/command/fix/fixer.go index fca464c5a..f82cc9393 100644 --- a/command/fix/fixer.go +++ b/command/fix/fixer.go @@ -15,10 +15,19 @@ 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), "virtualbox-gaattach": new(FixerVirtualBoxGAAttach), } + + FixerOrder = []string{ + "iso-md5", + "createtime", + "virtualbox-gaattach", + } } From 5e2f08de70a3dd9a3d47b1aa3729e72ee94cbe07 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 14:44:15 -0800 Subject: [PATCH 09/11] post-processor/vagrant: do overrides --- post-processor/vagrant/post-processor.go | 129 +++++++++++------- post-processor/vagrant/post-processor_test.go | 44 +++++- .../post-processors/vagrant.html.markdown | 29 ++++ 3 files changed, 148 insertions(+), 54 deletions(-) diff --git a/post-processor/vagrant/post-processor.go b/post-processor/vagrant/post-processor.go index 7772d69c0..2329eba3c 100644 --- a/post-processor/vagrant/post-processor.go +++ b/post-processor/vagrant/post-processor.go @@ -26,66 +26,39 @@ var builtins = map[string]string{ type Config struct { common.PackerConfig `mapstructure:",squash"` + CompressionLevel int `mapstructure:"compression_level"` Include []string `mapstructure:"include"` OutputPath string `mapstructure:"output"` - VagrantfileTemplate string `mapstructure:"vagrantfile_template"` - CompressionLevel int `mapstructure:"compression_level"` + Override map[string]interface{} + VagrantfileTemplate string `mapstructure:"vagrantfile_template"` tpl *packer.ConfigTemplate } type PostProcessor struct { - config Config + configs map[string]*Config } func (p *PostProcessor) 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 { + p.configs = make(map[string]*Config) + p.configs[""] = new(Config) + if err := p.configureSingle(p.configs[""], raws...); err != nil { return err } - p.config.tpl.UserVars = p.config.PackerUserVars - - // Defaults - if p.config.OutputPath == "" { - p.config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box" - } - - found := false - for _, k := range md.Keys { - 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, - } + // 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 - 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)) + 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 errs != nil && len(errs.Errors) > 0 { - return errs - } - return nil } @@ -102,11 +75,16 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac panic(fmt.Sprintf("bad provider name: %s", name)) } + 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 := p.config.tpl.Process(p.config.OutputPath, &outputPathTemplate{ + outputPath, err := config.tpl.Process(config.OutputPath, &outputPathTemplate{ ArtifactId: artifact.Id(), - BuildName: p.config.PackerBuildName, + BuildName: config.PackerBuildName, Provider: name, }) if err != nil { @@ -121,7 +99,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac defer os.RemoveAll(dir) // Copy all of the includes files into the temporary directory - for _, src := range p.config.Include { + 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 { @@ -143,10 +121,10 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac // Write our Vagrantfile var customVagrantfile string - if p.config.VagrantfileTemplate != "" { + if config.VagrantfileTemplate != "" { ui.Message(fmt.Sprintf( - "Using custom Vagrantfile: %s", p.config.VagrantfileTemplate)) - customBytes, err := ioutil.ReadFile(p.config.VagrantfileTemplate) + "Using custom Vagrantfile: %s", config.VagrantfileTemplate)) + customBytes, err := ioutil.ReadFile(config.VagrantfileTemplate) if err != nil { return nil, false, err } @@ -170,13 +148,64 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac } // Create the box - if err := DirToBox(outputPath, dir, ui, p.config.CompressionLevel); err != nil { + 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 + } + } + + if !found { + config.CompressionLevel = flate.DefaultCompression + } + + // Accumulate any errors + errs := common.CheckUnusedConfig(md) + + validates := map[string]*string{ + "output": &config.OutputPath, + "vagrantfile_template": &config.VagrantfileTemplate, + } + + 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 +} + func providerForName(name string) Provider { switch name { case "virtualbox": diff --git a/post-processor/vagrant/post-processor_test.go b/post-processor/vagrant/post-processor_test.go index 552c7ba02..0ef151ebe 100644 --- a/post-processor/vagrant/post-processor_test.go +++ b/post-processor/vagrant/post-processor_test.go @@ -42,8 +42,9 @@ func TestPostProcessorPrepare_compressionLevel(t *testing.T) { t.Fatalf("err: %s", err) } - if p.config.CompressionLevel != flate.DefaultCompression { - t.Fatalf("bad: %#v", p.config.CompressionLevel) + config := p.configs[""] + if config.CompressionLevel != flate.DefaultCompression { + t.Fatalf("bad: %#v", config.CompressionLevel) } // Set @@ -53,8 +54,9 @@ func TestPostProcessorPrepare_compressionLevel(t *testing.T) { t.Fatalf("err: %s", err) } - if p.config.CompressionLevel != 7 { - t.Fatalf("bad: %#v", p.config.CompressionLevel) + config = p.configs[""] + if config.CompressionLevel != 7 { + t.Fatalf("bad: %#v", config.CompressionLevel) } } @@ -77,6 +79,40 @@ func TestPostProcessorPrepare_outputPath(t *testing.T) { } } +func TestPostProcessorPrepare_subConfigs(t *testing.T) { + var p PostProcessor + + // Default + c := testConfig() + 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", diff --git a/website/source/docs/post-processors/vagrant.html.markdown b/website/source/docs/post-processors/vagrant.html.markdown index 2b5f4368f..d83a2b265 100644 --- a/website/source/docs/post-processors/vagrant.html.markdown +++ b/website/source/docs/post-processors/vagrant.html.markdown @@ -68,3 +68,32 @@ below, with more details about certain options in following sections. * `vagrantfile_template` (string) - Path to a template to use for the Vagrantfile that is packaged with the box. + +## Provider-Specific Overrides + +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. + +Specify overrides within the `override` configuration by provider name: + +```json +{ + "type": "vagrant", + + "compression_level": 1, + "override": { + "vmware": { + "compression_level": 0 + } + } +} +``` + +In the example above, the compression level will be set to 1 except for +VMware, where it will be set to 0. + +The available provider names are: `aws`, `digitalocean`, `virtualbox`, +and `vmware`. From 8819594386130f537dbadc3e538f4057951e7ec4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 14:54:00 -0800 Subject: [PATCH 10/11] command/fix: fix for overrides --- command/fix/fixer.go | 2 + command/fix/fixer_pp_vagrant_override.go | 75 ++++++++++++++++++ command/fix/fixer_pp_vagrant_override_test.go | 79 +++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 command/fix/fixer_pp_vagrant_override.go create mode 100644 command/fix/fixer_pp_vagrant_override_test.go diff --git a/command/fix/fixer.go b/command/fix/fixer.go index f82cc9393..fd4d947da 100644 --- a/command/fix/fixer.go +++ b/command/fix/fixer.go @@ -22,6 +22,7 @@ func init() { Fixers = map[string]Fixer{ "iso-md5": new(FixerISOMD5), "createtime": new(FixerCreateTime), + "pp-vagrant-override": new(FixerVagrantPPOverride), "virtualbox-gaattach": new(FixerVirtualBoxGAAttach), } @@ -29,5 +30,6 @@ func init() { "iso-md5", "createtime", "virtualbox-gaattach", + "pp-vagrant-override", } } diff --git a/command/fix/fixer_pp_vagrant_override.go b/command/fix/fixer_pp_vagrant_override.go new file mode 100644 index 000000000..36aadfa46 --- /dev/null +++ b/command/fix/fixer_pp_vagrant_override.go @@ -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` +} diff --git a/command/fix/fixer_pp_vagrant_override_test.go b/command/fix/fixer_pp_vagrant_override_test.go new file mode 100644 index 000000000..ae344911e --- /dev/null +++ b/command/fix/fixer_pp_vagrant_override_test.go @@ -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) + } +} From 99cbe1fc42e5e7300d85cab63d8f2d998d2179f7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Dec 2013 14:56:04 -0800 Subject: [PATCH 11/11] command/fix: update help --- command/fix/help.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/command/fix/help.go b/command/fix/help.go index 94b5c6a11..88ab34070 100644 --- a/command/fix/help.go +++ b/command/fix/help.go @@ -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. `