From 6783bc3fb0776a1cb23c6b092b86f1bf47692745 Mon Sep 17 00:00:00 2001 From: Chris Bednarski Date: Tue, 13 Oct 2015 02:13:49 -0700 Subject: [PATCH] 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" +} +`