From d09a9ab0c7d94f4f938195367fe3dc59165707de Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Mon, 17 Aug 2015 01:26:03 -0700 Subject: [PATCH 01/11] Implemented internal plugins - Internal plugins are compiled into the same packer binary and invoked through the plugin command - Search paths allow disk-based plugins to override and should function as normal - This should allow for a 94% space savings vs statically compiling all the plugins as separate binaries.. approximately 24mb vs 431mb --- command/plugin.go | 147 ++++++++++++++++++++++++++++++++++++++++++++++ commands.go | 6 ++ config.go | 55 ++++++++++++++++- 3 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 command/plugin.go diff --git a/command/plugin.go b/command/plugin.go new file mode 100644 index 000000000..76e97484e --- /dev/null +++ b/command/plugin.go @@ -0,0 +1,147 @@ +package command + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/mitchellh/packer/builder/amazon/chroot" + "github.com/mitchellh/packer/builder/amazon/ebs" + "github.com/mitchellh/packer/builder/amazon/instance" + "github.com/mitchellh/packer/builder/digitalocean" + "github.com/mitchellh/packer/builder/docker" + filebuilder "github.com/mitchellh/packer/builder/file" + "github.com/mitchellh/packer/builder/googlecompute" + "github.com/mitchellh/packer/builder/null" + "github.com/mitchellh/packer/builder/openstack" + parallelsiso "github.com/mitchellh/packer/builder/parallels/iso" + parallelspvm "github.com/mitchellh/packer/builder/parallels/pvm" + "github.com/mitchellh/packer/builder/qemu" + virtualboxiso "github.com/mitchellh/packer/builder/virtualbox/iso" + virtualboxovf "github.com/mitchellh/packer/builder/virtualbox/ovf" + vmwareiso "github.com/mitchellh/packer/builder/vmware/iso" + vmwarevmx "github.com/mitchellh/packer/builder/vmware/vmx" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/provisioner/ansible-local" + "github.com/mitchellh/packer/provisioner/chef-client" + "github.com/mitchellh/packer/provisioner/chef-solo" + fileprovisioner "github.com/mitchellh/packer/provisioner/file" + "github.com/mitchellh/packer/provisioner/powershell" + "github.com/mitchellh/packer/provisioner/puppet-masterless" + "github.com/mitchellh/packer/provisioner/puppet-server" + "github.com/mitchellh/packer/provisioner/salt-masterless" + "github.com/mitchellh/packer/provisioner/shell" + shelllocal "github.com/mitchellh/packer/provisioner/shell-local" + "github.com/mitchellh/packer/provisioner/windows-restart" + windowsshell "github.com/mitchellh/packer/provisioner/windows-shell" +) + +type PluginCommand struct { + Meta +} + +var Builders = map[string]packer.Builder{ + "amazon-chroot": new(chroot.Builder), + "amazon-ebs": new(ebs.Builder), + "amazon-instance": new(instance.Builder), + "digitalocean": new(digitalocean.Builder), + "docker": new(docker.Builder), + "file": new(filebuilder.Builder), + "googlecompute": new(googlecompute.Builder), + "null": new(null.Builder), + "openstack": new(openstack.Builder), + "parallels-iso": new(parallelsiso.Builder), + "parallels-pvm": new(parallelspvm.Builder), + "qemu": new(qemu.Builder), + "virtualbox-iso": new(virtualboxiso.Builder), + "virtualbox-ovf": new(virtualboxovf.Builder), + "vmware-iso": new(vmwareiso.Builder), + "vmware-vmx": new(vmwarevmx.Builder), +} + +var Provisioners = map[string]packer.Provisioner{ + "ansible-local": new(ansiblelocal.Provisioner), + "chef-client": new(chefclient.Provisioner), + "chef-solo": new(chefsolo.Provisioner), + "file": new(fileprovisioner.Provisioner), + "powershell": new(powershell.Provisioner), + "puppet-masterless": new(puppetmasterless.Provisioner), + "puppet-server": new(puppetserver.Provisioner), + "salt-masterless": new(saltmasterless.Provisioner), + "shell": new(shell.Provisioner), + "shell-local": new(shelllocal.Provisioner), + "windows-restart": new(restart.Provisioner), + "windows-shell": new(windowsshell.Provisioner), +} + +var PostProcessors = map[string]packer.PostProcessor{} + +func (c *PluginCommand) Run(args []string) int { + // This is an internal call so we're not going to do much error checking. + // If there's a problem we'll usually just crash. + log.Printf("args: %#v", args) + if len(args) != 1 { + c.Ui.Error("Wrong number of args") + os.Exit(1) + } + + // Plugin should be called like "packer-builder-amazon-ebs" so we'll take it + // apart. + parts := strings.Split(args[0], "-") + pluginType := parts[1] + pluginName := "" + // Post-processor is split so we'll so some magic here. We could use a + // regexp but this is simpler. + if pluginType == "post" { + pluginType = strings.Join(parts[1:2], "-") + pluginName = strings.Join(parts[3:], "-") + } else { + pluginName = strings.Join(parts[2:], "-") + } + + server, err := plugin.Server() + if err != nil { + panic(err) + } + + if pluginType == "builder" { + builder, found := Builders[pluginName] + if !found { + c.Ui.Error(fmt.Sprintf("Could not load builder: %s", pluginName)) + } + server.RegisterBuilder(builder) + } else if pluginType == "provisioner" { + provisioner, found := Provisioners[pluginName] + if !found { + c.Ui.Error(fmt.Sprintf("Could not load provisioner: %s", pluginName)) + } + server.RegisterProvisioner(provisioner) + } else if pluginType == "post-processor" { + postProcessor, found := PostProcessors[pluginName] + if !found { + c.Ui.Error(fmt.Sprintf("Could not load post-processor: %s", pluginName)) + } + server.RegisterPostProcessor(postProcessor) + } + + server.Serve() + + return 0 +} + +func (*PluginCommand) Help() string { + helpText := ` +Usage: packer plugin PLUGIN + + Runs an internally-compiled version of a plugin from the packer binary. Note + that this is an internal command and you should not call it yourself. +` + + return strings.TrimSpace(helpText) +} + +func (c *PluginCommand) Synopsis() string { + return "call an internal plugin" +} diff --git a/commands.go b/commands.go index 510250721..21ed30df9 100644 --- a/commands.go +++ b/commands.go @@ -59,6 +59,12 @@ func init() { CheckFunc: commandVersionCheck, }, nil }, + + "plugin": func() (cli.Command, error) { + return &command.PluginCommand{ + Meta: *CommandMeta, + }, nil + }, } } diff --git a/config.go b/config.go index efb4e7d31..004315489 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" "io" "log" "os/exec" @@ -10,6 +11,7 @@ import ( "strings" "github.com/mitchellh/osext" + "github.com/mitchellh/packer/command" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer/plugin" ) @@ -73,11 +75,17 @@ func (c *config) Discover() error { } } - // Last, look in the CWD. + // Next, look in the CWD. if err := c.discover("."); err != nil { return err } + // Finally, try to use an internal plugin. Note that this will not Override + // any previously-loaded plugins. + if err := c.discoverInternal(); err != nil { + return err + } + return nil } @@ -196,6 +204,41 @@ func (c *config) discoverSingle(glob string, m *map[string]string) error { return nil } +func (c *config) discoverInternal() error { + // Get the packer binary path + packerPath, err := osext.Executable() + if err != nil { + log.Printf("[ERR] Error loading exe directory: %s", err) + return err + } + + for builder := range command.Builders { + _, found := (c.Builders)[builder] + if !found { + log.Printf("Using internal plugin for %s", builder) + (c.Builders)[builder] = fmt.Sprintf("%s-PACKERSPACE-plugin-PACKERSPACE-packer-builder-%s", packerPath, builder) + } + } + + for provisioner := range command.Provisioners { + _, found := (c.Provisioners)[provisioner] + if !found { + log.Printf("Using internal plugin for %s", provisioner) + (c.Provisioners)[provisioner] = fmt.Sprintf("%s-PACKERSPACE-plugin-PACKERSPACE-packer-provisioner-%s", packerPath, provisioner) + } + } + + for postProcessor := range command.PostProcessors { + _, found := (c.PostProcessors)[postProcessor] + if !found { + log.Printf("Using internal plugin for %s", postProcessor) + (c.PostProcessors)[postProcessor] = fmt.Sprintf("%s-PACKERSPACE-plugin-PACKERSPACE-packer-post-processor-%s", packerPath, postProcessor) + } + } + + return nil +} + func (c *config) pluginClient(path string) *plugin.Client { originalPath := path @@ -214,6 +257,14 @@ func (c *config) pluginClient(path string) *plugin.Client { } } + // Check for special case using `packer plugin PLUGIN` + args := []string{} + if strings.Contains(path, "-PACKERSPACE-") { + parts := strings.Split(path, "-PACKERSPACE-") + path = parts[0] + args = parts[1:] + } + // If everything failed, just use the original path and let the error // bubble through. if path == "" { @@ -222,7 +273,7 @@ func (c *config) pluginClient(path string) *plugin.Client { log.Printf("Creating plugin client for path: %s", path) var config plugin.ClientConfig - config.Cmd = exec.Command(path) + config.Cmd = exec.Command(path, args...) config.Managed = true config.MinPort = c.PluginMinPort config.MaxPort = c.PluginMaxPort From 9fa93712a1e18a44c54b431c981dc2bde7cc7388 Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Mon, 17 Aug 2015 01:32:45 -0700 Subject: [PATCH 02/11] Added integrated post-processors --- command/plugin.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/command/plugin.go b/command/plugin.go index 76e97484e..1685db188 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -24,6 +24,16 @@ import ( vmwarevmx "github.com/mitchellh/packer/builder/vmware/vmx" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/post-processor/artifice" + "github.com/mitchellh/packer/post-processor/atlas" + "github.com/mitchellh/packer/post-processor/compress" + "github.com/mitchellh/packer/post-processor/docker-import" + "github.com/mitchellh/packer/post-processor/docker-push" + "github.com/mitchellh/packer/post-processor/docker-save" + "github.com/mitchellh/packer/post-processor/docker-tag" + "github.com/mitchellh/packer/post-processor/vagrant" + "github.com/mitchellh/packer/post-processor/vagrant-cloud" + "github.com/mitchellh/packer/post-processor/vsphere" "github.com/mitchellh/packer/provisioner/ansible-local" "github.com/mitchellh/packer/provisioner/chef-client" "github.com/mitchellh/packer/provisioner/chef-solo" @@ -76,7 +86,18 @@ var Provisioners = map[string]packer.Provisioner{ "windows-shell": new(windowsshell.Provisioner), } -var PostProcessors = map[string]packer.PostProcessor{} +var PostProcessors = map[string]packer.PostProcessor{ + "artifice": new(artifice.PostProcessor), + "atlas": new(atlas.PostProcessor), + "compress": new(compress.PostProcessor), + "docker-import": new(dockerimport.PostProcessor), + "docker-push": new(dockerpush.PostProcessor), + "docker-save": new(dockersave.PostProcessor), + "docker-tag": new(dockertag.PostProcessor), + "vagrant": new(vagrant.PostProcessor), + "vagrant-cloud": new(vagrantcloud.PostProcessor), + "vsphere": new(vsphere.PostProcessor), +} func (c *PluginCommand) Run(args []string) int { // This is an internal call so we're not going to do much error checking. From e080e73b04fc32017e6606e0760297b589e784a7 Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Mon, 17 Aug 2015 20:33:50 -0700 Subject: [PATCH 03/11] Add some exit codes and use a constant for -PACKERSPACE- --- command/plugin.go | 16 ++++++++++------ config.go | 21 +++++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/command/plugin.go b/command/plugin.go index 1685db188..57d6f0299 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -3,7 +3,6 @@ package command import ( "fmt" "log" - "os" "strings" "github.com/mitchellh/packer/builder/amazon/chroot" @@ -105,7 +104,7 @@ func (c *PluginCommand) Run(args []string) int { log.Printf("args: %#v", args) if len(args) != 1 { c.Ui.Error("Wrong number of args") - os.Exit(1) + return 1 } // Plugin should be called like "packer-builder-amazon-ebs" so we'll take it @@ -124,25 +123,29 @@ func (c *PluginCommand) Run(args []string) int { server, err := plugin.Server() if err != nil { - panic(err) + c.Ui.Error(fmt.Sprintf("Error starting plugin server: %s", err)) + return 1 } if pluginType == "builder" { builder, found := Builders[pluginName] if !found { c.Ui.Error(fmt.Sprintf("Could not load builder: %s", pluginName)) + return 1 } server.RegisterBuilder(builder) } else if pluginType == "provisioner" { provisioner, found := Provisioners[pluginName] if !found { c.Ui.Error(fmt.Sprintf("Could not load provisioner: %s", pluginName)) + return 1 } server.RegisterProvisioner(provisioner) } else if pluginType == "post-processor" { postProcessor, found := PostProcessors[pluginName] if !found { c.Ui.Error(fmt.Sprintf("Could not load post-processor: %s", pluginName)) + return 1 } server.RegisterPostProcessor(postProcessor) } @@ -156,13 +159,14 @@ func (*PluginCommand) Help() string { helpText := ` Usage: packer plugin PLUGIN - Runs an internally-compiled version of a plugin from the packer binary. Note - that this is an internal command and you should not call it yourself. + Runs an internally-compiled version of a plugin from the packer binary. + + NOTE: this is an internal command and you should not call it yourself. ` return strings.TrimSpace(helpText) } func (c *PluginCommand) Synopsis() string { - return "call an internal plugin" + return "internal plugin command" } diff --git a/config.go b/config.go index 004315489..a122b120d 100644 --- a/config.go +++ b/config.go @@ -16,6 +16,10 @@ import ( "github.com/mitchellh/packer/packer/plugin" ) +// PACKERSPACE is used to represent the spaces that separate args for a command +// without being confused with spaces in the path to the command itself. +const PACKERSPACE = "-PACKERSPACE-" + type config struct { DisableCheckpoint bool `json:"disable_checkpoint"` DisableCheckpointSignature bool `json:"disable_checkpoint_signature"` @@ -80,7 +84,7 @@ func (c *config) Discover() error { return err } - // Finally, try to use an internal plugin. Note that this will not Override + // Finally, try to use an internal plugin. Note that this will not override // any previously-loaded plugins. if err := c.discoverInternal(); err != nil { return err @@ -216,7 +220,8 @@ func (c *config) discoverInternal() error { _, found := (c.Builders)[builder] if !found { log.Printf("Using internal plugin for %s", builder) - (c.Builders)[builder] = fmt.Sprintf("%s-PACKERSPACE-plugin-PACKERSPACE-packer-builder-%s", packerPath, builder) + (c.Builders)[builder] = fmt.Sprintf("%s%splugin%spacker-builder-%s", + packerPath, PACKERSPACE, PACKERSPACE, builder) } } @@ -224,7 +229,9 @@ func (c *config) discoverInternal() error { _, found := (c.Provisioners)[provisioner] if !found { log.Printf("Using internal plugin for %s", provisioner) - (c.Provisioners)[provisioner] = fmt.Sprintf("%s-PACKERSPACE-plugin-PACKERSPACE-packer-provisioner-%s", packerPath, provisioner) + (c.Provisioners)[provisioner] = fmt.Sprintf( + "%s%splugin%spacker-provisioner-%s", + packerPath, PACKERSPACE, PACKERSPACE, provisioner) } } @@ -232,7 +239,9 @@ func (c *config) discoverInternal() error { _, found := (c.PostProcessors)[postProcessor] if !found { log.Printf("Using internal plugin for %s", postProcessor) - (c.PostProcessors)[postProcessor] = fmt.Sprintf("%s-PACKERSPACE-plugin-PACKERSPACE-packer-post-processor-%s", packerPath, postProcessor) + (c.PostProcessors)[postProcessor] = fmt.Sprintf( + "%s%splugin%spacker-post-processor-%s", + packerPath, PACKERSPACE, PACKERSPACE, postProcessor) } } @@ -259,8 +268,8 @@ func (c *config) pluginClient(path string) *plugin.Client { // Check for special case using `packer plugin PLUGIN` args := []string{} - if strings.Contains(path, "-PACKERSPACE-") { - parts := strings.Split(path, "-PACKERSPACE-") + if strings.Contains(path, PACKERSPACE) { + parts := strings.Split(path, PACKERSPACE) path = parts[0] args = parts[1:] } From 1641a5e0cb862dffb61d9d3a6bd669a91ebed47a Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Tue, 18 Aug 2015 13:46:54 -0700 Subject: [PATCH 04/11] Replace string splitting with a regexp; this is clearer and less code because of the post-processor case --- command/plugin.go | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/command/plugin.go b/command/plugin.go index 57d6f0299..60776fc54 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -3,6 +3,7 @@ package command import ( "fmt" "log" + "regexp" "strings" "github.com/mitchellh/packer/builder/amazon/chroot" @@ -98,28 +99,26 @@ var PostProcessors = map[string]packer.PostProcessor{ "vsphere": new(vsphere.PostProcessor), } +var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner)-(.+)") + func (c *PluginCommand) Run(args []string) int { - // This is an internal call so we're not going to do much error checking. - // If there's a problem we'll usually just crash. + // This is an internal call (users should not call this directly) so we're + // not going to do much input validation. If there's a problem we'll often + // just crash. Error handling should be added to facilitate debugging. log.Printf("args: %#v", args) if len(args) != 1 { c.Ui.Error("Wrong number of args") return 1 } - // Plugin should be called like "packer-builder-amazon-ebs" so we'll take it - // apart. - parts := strings.Split(args[0], "-") - pluginType := parts[1] - pluginName := "" - // Post-processor is split so we'll so some magic here. We could use a - // regexp but this is simpler. - if pluginType == "post" { - pluginType = strings.Join(parts[1:2], "-") - pluginName = strings.Join(parts[3:], "-") - } else { - pluginName = strings.Join(parts[2:], "-") + // Plugin will match something like "packer-builder-amazon-ebs" + parts := pluginRegexp.FindStringSubmatch(args[0]) + if len(parts) != 3 { + c.Ui.Error(fmt.Sprintf("Error parsing plugin argument [DEBUG]: %#v", parts)) + return 1 } + pluginType := parts[1] // capture group 1 (builder|post-processor|provisioner) + pluginName := parts[2] // capture group 2 (.+) server, err := plugin.Server() if err != nil { From bfe5b5b4b6d09995da7f7c3f6f6cdb14295fa9d0 Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Tue, 18 Aug 2015 15:21:11 -0700 Subject: [PATCH 05/11] Change if/else logic to switch --- command/plugin.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/command/plugin.go b/command/plugin.go index 60776fc54..91a8fe2b4 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -126,21 +126,22 @@ func (c *PluginCommand) Run(args []string) int { return 1 } - if pluginType == "builder" { + switch pluginType { + case "builder": builder, found := Builders[pluginName] if !found { c.Ui.Error(fmt.Sprintf("Could not load builder: %s", pluginName)) return 1 } server.RegisterBuilder(builder) - } else if pluginType == "provisioner" { + case "provisioner": provisioner, found := Provisioners[pluginName] if !found { c.Ui.Error(fmt.Sprintf("Could not load provisioner: %s", pluginName)) return 1 } server.RegisterProvisioner(provisioner) - } else if pluginType == "post-processor" { + case "post-processor": postProcessor, found := PostProcessors[pluginName] if !found { c.Ui.Error(fmt.Sprintf("Could not load post-processor: %s", pluginName)) From 7acdc1b6afee2446769c583e553d64e78ef2b466 Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Thu, 20 Aug 2015 16:43:30 -0700 Subject: [PATCH 06/11] Hide the plugin command from help output --- main.go | 23 ++++++++++++++++++++++- main_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index a0d3190d1..ac61d211b 100644 --- a/main.go +++ b/main.go @@ -181,7 +181,7 @@ func wrappedMain() int { cli := &cli.CLI{ Args: args, Commands: Commands, - HelpFunc: cli.BasicHelpFunc("packer"), + HelpFunc: excludeHelpFunc(Commands, []string{"plugin"}), HelpWriter: os.Stdout, Version: Version, } @@ -195,6 +195,27 @@ func wrappedMain() int { return exitCode } +// excludeHelpFunc filters commands we don't want to show from the list of +// commands displayed in packer's help text. +func excludeHelpFunc(commands map[string]cli.CommandFactory, exclude []string) cli.HelpFunc { + // Make search slice into a map so we can use use the `if found` idiom + // instead of a nested loop. + var excludes = make(map[string]interface{}, len(exclude)) + for _, item := range exclude { + excludes[item] = nil + } + + // Create filtered list of commands + helpCommands := []string{} + for command := range commands { + if _, found := excludes[command]; !found { + helpCommands = append(helpCommands, command) + } + } + + return cli.FilteredHelpFunc(helpCommands, cli.BasicHelpFunc("packer")) +} + // extractMachineReadable checks the args for the machine readable // flag and returns whether or not it is on. It modifies the args // to remove this flag. diff --git a/main_test.go b/main_test.go index 7a14bed19..2b6686b38 100644 --- a/main_test.go +++ b/main_test.go @@ -3,9 +3,36 @@ package main import ( "math/rand" "reflect" + "strings" "testing" + + "github.com/mitchellh/cli" + "github.com/mitchellh/packer/command" ) +func TestExcludeHelpFunc(t *testing.T) { + commands := map[string]cli.CommandFactory{ + "build": func() (cli.Command, error) { + return &command.BuildCommand{ + Meta: command.Meta{}, + }, nil + }, + + "fix": func() (cli.Command, error) { + return &command.FixCommand{ + Meta: command.Meta{}, + }, nil + }, + } + + helpFunc := excludeHelpFunc(commands, []string{"fix"}) + helpText := helpFunc(commands) + + if strings.Contains(helpText, "fix") { + t.Fatal("Found fix in help text even though we excluded it: \n\n%s\n\n", helpText) + } +} + func TestExtractMachineReadable(t *testing.T) { var args, expected, result []string var mr bool From 95797d7a8ab9c00ec39fc3a3eaf9f9e8052c586e Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Mon, 12 Oct 2015 19:14:01 -0700 Subject: [PATCH 07/11] Fatal -> Fatalf since we have a format string --- main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main_test.go b/main_test.go index 2b6686b38..0bc7ba96f 100644 --- a/main_test.go +++ b/main_test.go @@ -29,7 +29,7 @@ func TestExcludeHelpFunc(t *testing.T) { helpText := helpFunc(commands) if strings.Contains(helpText, "fix") { - t.Fatal("Found fix in help text even though we excluded it: \n\n%s\n\n", helpText) + t.Fatalf("Found fix in help text even though we excluded it: \n\n%s\n\n", helpText) } } From 6783bc3fb0776a1cb23c6b092b86f1bf47692745 Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Tue, 13 Oct 2015 02:13:49 -0700 Subject: [PATCH 08/11] Added generator for command/plugin.go so we don't have to edit it by hand to add new plugins --- command/plugin.go | 159 +++++++++--------- command/version.go | 2 + scripts/generate-plugins.go | 325 ++++++++++++++++++++++++++++++++++++ 3 files changed, 411 insertions(+), 75 deletions(-) create mode 100644 scripts/generate-plugins.go diff --git a/command/plugin.go b/command/plugin.go index 91a8fe2b4..cf213b3ef 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -1,3 +1,7 @@ +// +// This file is automatically generated by scripts/generate-plugins.go -- Do not edit! +// + package command import ( @@ -6,46 +10,48 @@ import ( "regexp" "strings" - "github.com/mitchellh/packer/builder/amazon/chroot" - "github.com/mitchellh/packer/builder/amazon/ebs" - "github.com/mitchellh/packer/builder/amazon/instance" - "github.com/mitchellh/packer/builder/digitalocean" - "github.com/mitchellh/packer/builder/docker" - filebuilder "github.com/mitchellh/packer/builder/file" - "github.com/mitchellh/packer/builder/googlecompute" - "github.com/mitchellh/packer/builder/null" - "github.com/mitchellh/packer/builder/openstack" - parallelsiso "github.com/mitchellh/packer/builder/parallels/iso" - parallelspvm "github.com/mitchellh/packer/builder/parallels/pvm" - "github.com/mitchellh/packer/builder/qemu" - virtualboxiso "github.com/mitchellh/packer/builder/virtualbox/iso" - virtualboxovf "github.com/mitchellh/packer/builder/virtualbox/ovf" - vmwareiso "github.com/mitchellh/packer/builder/vmware/iso" - vmwarevmx "github.com/mitchellh/packer/builder/vmware/vmx" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer/plugin" - "github.com/mitchellh/packer/post-processor/artifice" - "github.com/mitchellh/packer/post-processor/atlas" - "github.com/mitchellh/packer/post-processor/compress" - "github.com/mitchellh/packer/post-processor/docker-import" - "github.com/mitchellh/packer/post-processor/docker-push" - "github.com/mitchellh/packer/post-processor/docker-save" - "github.com/mitchellh/packer/post-processor/docker-tag" - "github.com/mitchellh/packer/post-processor/vagrant" - "github.com/mitchellh/packer/post-processor/vagrant-cloud" - "github.com/mitchellh/packer/post-processor/vsphere" - "github.com/mitchellh/packer/provisioner/ansible-local" - "github.com/mitchellh/packer/provisioner/chef-client" - "github.com/mitchellh/packer/provisioner/chef-solo" + + amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot" + amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs" + amazoninstancebuilder "github.com/mitchellh/packer/builder/amazon/instance" + ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local" + artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice" + atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas" + chefclientprovisioner "github.com/mitchellh/packer/provisioner/chef-client" + chefsoloprovisioner "github.com/mitchellh/packer/provisioner/chef-solo" + compresspostprocessor "github.com/mitchellh/packer/post-processor/compress" + digitaloceanbuilder "github.com/mitchellh/packer/builder/digitalocean" + dockerbuilder "github.com/mitchellh/packer/builder/docker" + dockerimportpostprocessor "github.com/mitchellh/packer/post-processor/docker-import" + dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push" + dockersavepostprocessor "github.com/mitchellh/packer/post-processor/docker-save" + dockertagpostprocessor "github.com/mitchellh/packer/post-processor/docker-tag" + filebuilder "github.com/mitchellh/packer/builder/file" fileprovisioner "github.com/mitchellh/packer/provisioner/file" - "github.com/mitchellh/packer/provisioner/powershell" - "github.com/mitchellh/packer/provisioner/puppet-masterless" - "github.com/mitchellh/packer/provisioner/puppet-server" - "github.com/mitchellh/packer/provisioner/salt-masterless" - "github.com/mitchellh/packer/provisioner/shell" - shelllocal "github.com/mitchellh/packer/provisioner/shell-local" - "github.com/mitchellh/packer/provisioner/windows-restart" - windowsshell "github.com/mitchellh/packer/provisioner/windows-shell" + googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" + nullbuilder "github.com/mitchellh/packer/builder/null" + openstackbuilder "github.com/mitchellh/packer/builder/openstack" + parallelsisobuilder "github.com/mitchellh/packer/builder/parallels/iso" + parallelspvmbuilder "github.com/mitchellh/packer/builder/parallels/pvm" + powershellprovisioner "github.com/mitchellh/packer/provisioner/powershell" + puppetmasterlessprovisioner "github.com/mitchellh/packer/provisioner/puppet-masterless" + puppetserverprovisioner "github.com/mitchellh/packer/provisioner/puppet-server" + qemubuilder "github.com/mitchellh/packer/builder/qemu" + saltmasterlessprovisioner "github.com/mitchellh/packer/provisioner/salt-masterless" + shelllocalprovisioner "github.com/mitchellh/packer/provisioner/shell-local" + shellprovisioner "github.com/mitchellh/packer/provisioner/shell" + vagrantcloudpostprocessor "github.com/mitchellh/packer/post-processor/vagrant-cloud" + vagrantpostprocessor "github.com/mitchellh/packer/post-processor/vagrant" + virtualboxisobuilder "github.com/mitchellh/packer/builder/virtualbox/iso" + virtualboxovfbuilder "github.com/mitchellh/packer/builder/virtualbox/ovf" + vmwareisobuilder "github.com/mitchellh/packer/builder/vmware/iso" + vmwarevmxbuilder "github.com/mitchellh/packer/builder/vmware/vmx" + vspherepostprocessor "github.com/mitchellh/packer/post-processor/vsphere" + windowsrestartprovisioner "github.com/mitchellh/packer/provisioner/windows-restart" + windowsshellprovisioner "github.com/mitchellh/packer/provisioner/windows-shell" + ) type PluginCommand struct { @@ -53,52 +59,55 @@ type PluginCommand struct { } var Builders = map[string]packer.Builder{ - "amazon-chroot": new(chroot.Builder), - "amazon-ebs": new(ebs.Builder), - "amazon-instance": new(instance.Builder), - "digitalocean": new(digitalocean.Builder), - "docker": new(docker.Builder), - "file": new(filebuilder.Builder), - "googlecompute": new(googlecompute.Builder), - "null": new(null.Builder), - "openstack": new(openstack.Builder), - "parallels-iso": new(parallelsiso.Builder), - "parallels-pvm": new(parallelspvm.Builder), - "qemu": new(qemu.Builder), - "virtualbox-iso": new(virtualboxiso.Builder), - "virtualbox-ovf": new(virtualboxovf.Builder), - "vmware-iso": new(vmwareiso.Builder), - "vmware-vmx": new(vmwarevmx.Builder), + "amazon-chroot": new(amazonchrootbuilder.Builder), + "amazon-ebs": new(amazonebsbuilder.Builder), + "amazon-instance": new(amazoninstancebuilder.Builder), + "digitalocean": new(digitaloceanbuilder.Builder), + "docker": new(dockerbuilder.Builder), + "file": new(filebuilder.Builder), + "googlecompute": new(googlecomputebuilder.Builder), + "null": new(nullbuilder.Builder), + "openstack": new(openstackbuilder.Builder), + "parallels-iso": new(parallelsisobuilder.Builder), + "parallels-pvm": new(parallelspvmbuilder.Builder), + "qemu": new(qemubuilder.Builder), + "virtualbox-iso": new(virtualboxisobuilder.Builder), + "virtualbox-ovf": new(virtualboxovfbuilder.Builder), + "vmware-iso": new(vmwareisobuilder.Builder), + "vmware-vmx": new(vmwarevmxbuilder.Builder), } + var Provisioners = map[string]packer.Provisioner{ - "ansible-local": new(ansiblelocal.Provisioner), - "chef-client": new(chefclient.Provisioner), - "chef-solo": new(chefsolo.Provisioner), - "file": new(fileprovisioner.Provisioner), - "powershell": new(powershell.Provisioner), - "puppet-masterless": new(puppetmasterless.Provisioner), - "puppet-server": new(puppetserver.Provisioner), - "salt-masterless": new(saltmasterless.Provisioner), - "shell": new(shell.Provisioner), - "shell-local": new(shelllocal.Provisioner), - "windows-restart": new(restart.Provisioner), - "windows-shell": new(windowsshell.Provisioner), + "ansible-local": new(ansiblelocalprovisioner.Provisioner), + "chef-client": new(chefclientprovisioner.Provisioner), + "chef-solo": new(chefsoloprovisioner.Provisioner), + "file": new(fileprovisioner.Provisioner), + "powershell": new(powershellprovisioner.Provisioner), + "puppet-masterless": new(puppetmasterlessprovisioner.Provisioner), + "puppet-server": new(puppetserverprovisioner.Provisioner), + "salt-masterless": new(saltmasterlessprovisioner.Provisioner), + "shell": new(shellprovisioner.Provisioner), + "shell-local": new(shelllocalprovisioner.Provisioner), + "windows-restart": new(windowsrestartprovisioner.Provisioner), + "windows-shell": new(windowsshellprovisioner.Provisioner), } + var PostProcessors = map[string]packer.PostProcessor{ - "artifice": new(artifice.PostProcessor), - "atlas": new(atlas.PostProcessor), - "compress": new(compress.PostProcessor), - "docker-import": new(dockerimport.PostProcessor), - "docker-push": new(dockerpush.PostProcessor), - "docker-save": new(dockersave.PostProcessor), - "docker-tag": new(dockertag.PostProcessor), - "vagrant": new(vagrant.PostProcessor), - "vagrant-cloud": new(vagrantcloud.PostProcessor), - "vsphere": new(vsphere.PostProcessor), + "artifice": new(artificepostprocessor.PostProcessor), + "atlas": new(atlaspostprocessor.PostProcessor), + "compress": new(compresspostprocessor.PostProcessor), + "docker-import": new(dockerimportpostprocessor.PostProcessor), + "docker-push": new(dockerpushpostprocessor.PostProcessor), + "docker-save": new(dockersavepostprocessor.PostProcessor), + "docker-tag": new(dockertagpostprocessor.PostProcessor), + "vagrant": new(vagrantpostprocessor.PostProcessor), + "vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor), + "vsphere": new(vspherepostprocessor.PostProcessor), } + var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner)-(.+)") func (c *PluginCommand) Run(args []string) int { diff --git a/command/version.go b/command/version.go index cd170f2df..f5898e77d 100644 --- a/command/version.go +++ b/command/version.go @@ -1,5 +1,7 @@ package command +//go:generate go run ../scripts/generate-plugins.go + import ( "bytes" "fmt" diff --git a/scripts/generate-plugins.go b/scripts/generate-plugins.go new file mode 100644 index 000000000..62828f96b --- /dev/null +++ b/scripts/generate-plugins.go @@ -0,0 +1,325 @@ +// Generate Plugins is a small program that updates the lists of plugins in +// command/plugin.go so they will be compiled into the main packer binary. +// +// See https://github.com/mitchellh/packer/pull/2608 for details. +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "log" + "os" + "path/filepath" + "sort" + "strings" +) + +const target = "command/plugin.go" + +func main() { + wd, _ := os.Getwd() + if filepath.Base(wd) != "packer" { + os.Chdir("..") + wd, _ = os.Getwd() + if filepath.Base(wd) != "packer" { + log.Fatalf("This program must be invoked in the packer project root; in %s", wd) + } + } + + builders, err := discoverBuilders() + if err != nil { + log.Fatalf("Failed to discover builders: %s", err) + } + + provisioners, _ := discoverProvisioners() + if err != nil { + log.Fatalf("Failed to discover provisioners: %s", err) + } + + postProcessors, _ := discoverPostProcessors() + if err != nil { + log.Fatalf("Failed to discover post processors: %s", err) + } + + output := source + output = strings.Replace(output, "IMPORTS", makeImports(builders, provisioners, postProcessors), 1) + output = strings.Replace(output, "BUILDERS", makeMap("Builders", "Builder", builders), 1) + output = strings.Replace(output, "PROVISIONERS", makeMap("Provisioners", "Provisioner", provisioners), 1) + output = strings.Replace(output, "POSTPROCESSORS", makeMap("PostProcessors", "PostProcessor", postProcessors), 1) + + // TODO sort the lists of plugins so we are not subjected to random OS ordering of the plugin lists + // TODO format the file + + file, err := os.Create(target) + if err != nil { + log.Fatalf("Failed to open %s for writing: %s", target, err) + } + file.WriteString(output) + file.Close() + + log.Printf("Generated %s", target) +} + +type plugin struct { + Package string + PluginName string + TypeName string + Path string + ImportName string +} + +// makeMap creates a map named Name with type packer.Name that looks something +// like this: +// +// var Builders = map[string]packer.Builder{ +// "amazon-chroot": new(chroot.Builder), +// "amazon-ebs": new(ebs.Builder), +// "amazon-instance": new(instance.Builder), +func makeMap(varName, varType string, items []plugin) string { + output := "" + + output += fmt.Sprintf("var %s = map[string]packer.%s{\n", varName, varType) + for _, item := range items { + output += fmt.Sprintf("\t\"%s\": new(%s.%s),\n", item.PluginName, item.ImportName, item.TypeName) + } + output += "}\n" + return output +} + +func makeImports(builders, provisioners, postProcessors []plugin) string { + plugins := []string{} + + for _, builder := range builders { + plugins = append(plugins, fmt.Sprintf("\t%s \"github.com/mitchellh/packer/%s\"\n", builder.ImportName, builder.Path)) + } + + for _, provisioner := range provisioners { + plugins = append(plugins, fmt.Sprintf("\t%s \"github.com/mitchellh/packer/%s\"\n", provisioner.ImportName, provisioner.Path)) + } + + for _, postProcessor := range postProcessors { + plugins = append(plugins, fmt.Sprintf("\t%s \"github.com/mitchellh/packer/%s\"\n", postProcessor.ImportName, postProcessor.Path)) + } + + // Make things pretty + sort.Strings(plugins) + + return strings.Join(plugins, "") +} + +// listDirectories recursively lists directories under the specified path +func listDirectories(path string) ([]string, error) { + names := []string{} + items, err := ioutil.ReadDir(path) + if err != nil { + return names, err + } + + for _, item := range items { + // We only want directories + if item.IsDir() { + currentDir := filepath.Join(path, item.Name()) + names = append(names, currentDir) + + // Do some recursion + subNames, err := listDirectories(currentDir) + if err == nil { + names = append(names, subNames...) + } + } + } + + return names, nil +} + +// deriveName determines the name of the plugin (what you'll see in a packer +// template) based on the filesystem path. We use two rules: +// +// Start with -> builder/virtualbox/iso +// +// 1. Strip the root -> virtualbox/iso +// 2. Switch slash / to dash - -> virtualbox-iso +func deriveName(root, full string) string { + short, _ := filepath.Rel(root, full) + bits := strings.Split(short, string(os.PathSeparator)) + return strings.Join(bits, "-") +} + +// deriveImport will build a unique import identifier based on packageName and +// the result of deriveName() +// +// This will be something like -> virtualboxisobuilder +// +// Which is long, but deterministic and unique. +func deriveImport(typeName, derivedName string) string { + return strings.Replace(derivedName, "-", "", -1) + strings.ToLower(typeName) +} + +// discoverTypesInPath searches for types of typeID in path and returns a list +// of plugins it finds. +func discoverTypesInPath(path, typeID string) ([]plugin, error) { + postProcessors := []plugin{} + + dirs, err := listDirectories(path) + if err != nil { + return postProcessors, err + } + + for _, dir := range dirs { + fset := token.NewFileSet() + goPackages, err := parser.ParseDir(fset, dir, nil, parser.AllErrors) + if err != nil { + return postProcessors, fmt.Errorf("Failed parsing directory %s: %s", dir, err) + } + + for _, goPackage := range goPackages { + ast.PackageExports(goPackage) + ast.Inspect(goPackage, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.TypeSpec: + if x.Name.Name == typeID { + derivedName := deriveName(path, dir) + postProcessors = append(postProcessors, plugin{ + Package: goPackage.Name, + PluginName: derivedName, + ImportName: deriveImport(x.Name.Name, derivedName), + TypeName: x.Name.Name, + Path: dir, + }) + // The AST stops parsing when we return false. Once we + // find the symbol we want we can stop parsing. + + // DEBUG: + // fmt.Printf("package %#v\n", goPackage) + return false + } + } + return true + }) + } + } + + return postProcessors, nil +} + +func discoverBuilders() ([]plugin, error) { + path := "./builder" + typeID := "Builder" + return discoverTypesInPath(path, typeID) +} + +func discoverProvisioners() ([]plugin, error) { + path := "./provisioner" + typeID := "Provisioner" + return discoverTypesInPath(path, typeID) +} + +func discoverPostProcessors() ([]plugin, error) { + path := "./post-processor" + typeID := "PostProcessor" + return discoverTypesInPath(path, typeID) +} + +const source = `// +// This file is automatically generated by scripts/generate-plugins.go -- Do not edit! +// + +package command + +import ( + "fmt" + "log" + "regexp" + "strings" + + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/packer/plugin" + +IMPORTS +) + +type PluginCommand struct { + Meta +} + +BUILDERS + +PROVISIONERS + +POSTPROCESSORS + +var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner)-(.+)") + +func (c *PluginCommand) Run(args []string) int { + // This is an internal call (users should not call this directly) so we're + // not going to do much input validation. If there's a problem we'll often + // just crash. Error handling should be added to facilitate debugging. + log.Printf("args: %#v", args) + if len(args) != 1 { + c.Ui.Error("Wrong number of args") + return 1 + } + + // Plugin will match something like "packer-builder-amazon-ebs" + parts := pluginRegexp.FindStringSubmatch(args[0]) + if len(parts) != 3 { + c.Ui.Error(fmt.Sprintf("Error parsing plugin argument [DEBUG]: %#v", parts)) + return 1 + } + pluginType := parts[1] // capture group 1 (builder|post-processor|provisioner) + pluginName := parts[2] // capture group 2 (.+) + + server, err := plugin.Server() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error starting plugin server: %s", err)) + return 1 + } + + switch pluginType { + case "builder": + builder, found := Builders[pluginName] + if !found { + c.Ui.Error(fmt.Sprintf("Could not load builder: %s", pluginName)) + return 1 + } + server.RegisterBuilder(builder) + case "provisioner": + provisioner, found := Provisioners[pluginName] + if !found { + c.Ui.Error(fmt.Sprintf("Could not load provisioner: %s", pluginName)) + return 1 + } + server.RegisterProvisioner(provisioner) + case "post-processor": + postProcessor, found := PostProcessors[pluginName] + if !found { + c.Ui.Error(fmt.Sprintf("Could not load post-processor: %s", pluginName)) + return 1 + } + server.RegisterPostProcessor(postProcessor) + } + + server.Serve() + + return 0 +} + +func (*PluginCommand) Help() string { + helpText := ` + "`" + ` +Usage: packer plugin PLUGIN + + Runs an internally-compiled version of a plugin from the packer binary. + + NOTE: this is an internal command and you should not call it yourself. +` + "`" + ` + + return strings.TrimSpace(helpText) +} + +func (c *PluginCommand) Synopsis() string { + return "internal plugin command" +} +` From 9c68f039b37b2ad76205a4100f1c9fe8acd0072c Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Tue, 13 Oct 2015 02:26:07 -0700 Subject: [PATCH 09/11] Don't suppress errors from provisioners or post-processors --- scripts/generate-plugins.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generate-plugins.go b/scripts/generate-plugins.go index 62828f96b..c0c8fc185 100644 --- a/scripts/generate-plugins.go +++ b/scripts/generate-plugins.go @@ -34,12 +34,12 @@ func main() { log.Fatalf("Failed to discover builders: %s", err) } - provisioners, _ := discoverProvisioners() + provisioners, err := discoverProvisioners() if err != nil { log.Fatalf("Failed to discover provisioners: %s", err) } - postProcessors, _ := discoverPostProcessors() + postProcessors, err := discoverPostProcessors() if err != nil { log.Fatalf("Failed to discover post processors: %s", err) } From eb8a0bf731ef842dad2687da83dbdd499e8c7a2c Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Tue, 13 Oct 2015 02:38:31 -0700 Subject: [PATCH 10/11] Add some documentation to generate-plugins --- scripts/generate-plugins.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/scripts/generate-plugins.go b/scripts/generate-plugins.go index c0c8fc185..2dced41a3 100644 --- a/scripts/generate-plugins.go +++ b/scripts/generate-plugins.go @@ -20,6 +20,9 @@ import ( const target = "command/plugin.go" func main() { + // Normally this is run via go:generate from the command folder so we need + // to cd .. first. But when developing it's easier to use go run, so we'll + // support that too. wd, _ := os.Getwd() if filepath.Base(wd) != "packer" { os.Chdir("..") @@ -29,6 +32,7 @@ func main() { } } + // Collect all of the data we need about plugins we have in the project builders, err := discoverBuilders() if err != nil { log.Fatalf("Failed to discover builders: %s", err) @@ -44,6 +48,7 @@ func main() { log.Fatalf("Failed to discover post processors: %s", err) } + // Do some simple code generation and templating output := source output = strings.Replace(output, "IMPORTS", makeImports(builders, provisioners, postProcessors), 1) output = strings.Replace(output, "BUILDERS", makeMap("Builders", "Builder", builders), 1) @@ -53,22 +58,27 @@ func main() { // TODO sort the lists of plugins so we are not subjected to random OS ordering of the plugin lists // TODO format the file + // Write our generated code to the command/plugin.go file file, err := os.Create(target) + defer file.Close() if err != nil { log.Fatalf("Failed to open %s for writing: %s", target, err) } - file.WriteString(output) - file.Close() + + _, err = file.WriteString(output) + if err != nil { + log.Fatalf("Failed writing to %s: %s", target, err) + } log.Printf("Generated %s", target) } type plugin struct { - Package string - PluginName string - TypeName string - Path string - ImportName string + Package string // This plugin's package name (iso) + PluginName string // Name of plugin (vmware-iso) + TypeName string // Type of plugin (builder) + Path string // Path relative to packer root (builder/vmware/iso) + ImportName string // PluginName+TypeName (vmwareisobuilder) } // makeMap creates a map named Name with type packer.Name that looks something From a143f1e0856d0425d43fffa7d82b6b5e59f2c68c Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Wed, 21 Oct 2015 16:41:58 -0700 Subject: [PATCH 11/11] Updated build.sh so it doesn't build all the plugins separately anymore --- scripts/build.sh | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index b2e3248e4..dcd9bd7c8 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -42,18 +42,10 @@ gox \ -os="${XC_OS}" \ -arch="${XC_ARCH}" \ -ldflags "-X main.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \ - -output "pkg/{{.OS}}_{{.Arch}}/packer-{{.Dir}}" \ - ./... + -output "pkg/{{.OS}}_{{.Arch}}/packer" \ + . set -e -# Make sure "packer-packer" is renamed properly -for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do - set +e - mv ${PLATFORM}/packer-packer.exe ${PLATFORM}/packer.exe 2>/dev/null - mv ${PLATFORM}/packer-packer ${PLATFORM}/packer 2>/dev/null - set -e -done - # Move all the compiled things to the $GOPATH/bin GOPATH=${GOPATH:-$(go env GOPATH)} case $(uname) in