github.com/kimor79/packer@v0.8.7-0.20151221212622-d507b18eb4cf/scripts/generate-plugins.go (about)

     1  // Generate Plugins is a small program that updates the lists of plugins in
     2  // command/plugin.go so they will be compiled into the main packer binary.
     3  //
     4  // See https://github.com/mitchellh/packer/pull/2608 for details.
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/parser"
    11  	"go/token"
    12  	"io/ioutil"
    13  	"log"
    14  	"os"
    15  	"path/filepath"
    16  	"sort"
    17  	"strings"
    18  )
    19  
    20  const target = "command/plugin.go"
    21  
    22  func main() {
    23  	// Normally this is run via go:generate from the command folder so we need
    24  	// to cd .. first. But when developing it's easier to use go run, so we'll
    25  	// support that too.
    26  	wd, _ := os.Getwd()
    27  	if filepath.Base(wd) != "packer" {
    28  		os.Chdir("..")
    29  		wd, _ = os.Getwd()
    30  		if filepath.Base(wd) != "packer" {
    31  			log.Fatalf("This program must be invoked in the packer project root; in %s", wd)
    32  		}
    33  	}
    34  
    35  	// Collect all of the data we need about plugins we have in the project
    36  	builders, err := discoverBuilders()
    37  	if err != nil {
    38  		log.Fatalf("Failed to discover builders: %s", err)
    39  	}
    40  
    41  	provisioners, err := discoverProvisioners()
    42  	if err != nil {
    43  		log.Fatalf("Failed to discover provisioners: %s", err)
    44  	}
    45  
    46  	postProcessors, err := discoverPostProcessors()
    47  	if err != nil {
    48  		log.Fatalf("Failed to discover post processors: %s", err)
    49  	}
    50  
    51  	// Do some simple code generation and templating
    52  	output := source
    53  	output = strings.Replace(output, "IMPORTS", makeImports(builders, provisioners, postProcessors), 1)
    54  	output = strings.Replace(output, "BUILDERS", makeMap("Builders", "Builder", builders), 1)
    55  	output = strings.Replace(output, "PROVISIONERS", makeMap("Provisioners", "Provisioner", provisioners), 1)
    56  	output = strings.Replace(output, "POSTPROCESSORS", makeMap("PostProcessors", "PostProcessor", postProcessors), 1)
    57  
    58  	// TODO sort the lists of plugins so we are not subjected to random OS ordering of the plugin lists
    59  	// TODO format the file
    60  
    61  	// Write our generated code to the command/plugin.go file
    62  	file, err := os.Create(target)
    63  	defer file.Close()
    64  	if err != nil {
    65  		log.Fatalf("Failed to open %s for writing: %s", target, err)
    66  	}
    67  
    68  	_, err = file.WriteString(output)
    69  	if err != nil {
    70  		log.Fatalf("Failed writing to %s: %s", target, err)
    71  	}
    72  
    73  	log.Printf("Generated %s", target)
    74  }
    75  
    76  type plugin struct {
    77  	Package    string // This plugin's package name (iso)
    78  	PluginName string // Name of plugin (vmware-iso)
    79  	TypeName   string // Type of plugin (builder)
    80  	Path       string // Path relative to packer root (builder/vmware/iso)
    81  	ImportName string // PluginName+TypeName (vmwareisobuilder)
    82  }
    83  
    84  // makeMap creates a map named Name with type packer.Name that looks something
    85  // like this:
    86  //
    87  // var Builders = map[string]packer.Builder{
    88  // 	"amazon-chroot":   new(chroot.Builder),
    89  // 	"amazon-ebs":      new(ebs.Builder),
    90  // 	"amazon-instance": new(instance.Builder),
    91  func makeMap(varName, varType string, items []plugin) string {
    92  	output := ""
    93  
    94  	output += fmt.Sprintf("var %s = map[string]packer.%s{\n", varName, varType)
    95  	for _, item := range items {
    96  		output += fmt.Sprintf("\t\"%s\":   new(%s.%s),\n", item.PluginName, item.ImportName, item.TypeName)
    97  	}
    98  	output += "}\n"
    99  	return output
   100  }
   101  
   102  func makeImports(builders, provisioners, postProcessors []plugin) string {
   103  	plugins := []string{}
   104  
   105  	for _, builder := range builders {
   106  		plugins = append(plugins, fmt.Sprintf("\t%s \"github.com/mitchellh/packer/%s\"\n", builder.ImportName, builder.Path))
   107  	}
   108  
   109  	for _, provisioner := range provisioners {
   110  		plugins = append(plugins, fmt.Sprintf("\t%s \"github.com/mitchellh/packer/%s\"\n", provisioner.ImportName, provisioner.Path))
   111  	}
   112  
   113  	for _, postProcessor := range postProcessors {
   114  		plugins = append(plugins, fmt.Sprintf("\t%s \"github.com/mitchellh/packer/%s\"\n", postProcessor.ImportName, postProcessor.Path))
   115  	}
   116  
   117  	// Make things pretty
   118  	sort.Strings(plugins)
   119  
   120  	return strings.Join(plugins, "")
   121  }
   122  
   123  // listDirectories recursively lists directories under the specified path
   124  func listDirectories(path string) ([]string, error) {
   125  	names := []string{}
   126  	items, err := ioutil.ReadDir(path)
   127  	if err != nil {
   128  		return names, err
   129  	}
   130  
   131  	for _, item := range items {
   132  		// We only want directories
   133  		if item.IsDir() {
   134  			currentDir := filepath.Join(path, item.Name())
   135  			names = append(names, currentDir)
   136  
   137  			// Do some recursion
   138  			subNames, err := listDirectories(currentDir)
   139  			if err == nil {
   140  				names = append(names, subNames...)
   141  			}
   142  		}
   143  	}
   144  
   145  	return names, nil
   146  }
   147  
   148  // deriveName determines the name of the plugin (what you'll see in a packer
   149  // template) based on the filesystem path. We use two rules:
   150  //
   151  // Start with                     -> builder/virtualbox/iso
   152  //
   153  // 1. Strip the root              -> virtualbox/iso
   154  // 2. Switch slash / to dash -    -> virtualbox-iso
   155  func deriveName(root, full string) string {
   156  	short, _ := filepath.Rel(root, full)
   157  	bits := strings.Split(short, string(os.PathSeparator))
   158  	return strings.Join(bits, "-")
   159  }
   160  
   161  // deriveImport will build a unique import identifier based on packageName and
   162  // the result of deriveName()
   163  //
   164  // This will be something like    -> virtualboxisobuilder
   165  //
   166  // Which is long, but deterministic and unique.
   167  func deriveImport(typeName, derivedName string) string {
   168  	return strings.Replace(derivedName, "-", "", -1) + strings.ToLower(typeName)
   169  }
   170  
   171  // discoverTypesInPath searches for types of typeID in path and returns a list
   172  // of plugins it finds.
   173  func discoverTypesInPath(path, typeID string) ([]plugin, error) {
   174  	postProcessors := []plugin{}
   175  
   176  	dirs, err := listDirectories(path)
   177  	if err != nil {
   178  		return postProcessors, err
   179  	}
   180  
   181  	for _, dir := range dirs {
   182  		fset := token.NewFileSet()
   183  		goPackages, err := parser.ParseDir(fset, dir, nil, parser.AllErrors)
   184  		if err != nil {
   185  			return postProcessors, fmt.Errorf("Failed parsing directory %s: %s", dir, err)
   186  		}
   187  
   188  		for _, goPackage := range goPackages {
   189  			ast.PackageExports(goPackage)
   190  			ast.Inspect(goPackage, func(n ast.Node) bool {
   191  				switch x := n.(type) {
   192  				case *ast.TypeSpec:
   193  					if x.Name.Name == typeID {
   194  						derivedName := deriveName(path, dir)
   195  						postProcessors = append(postProcessors, plugin{
   196  							Package:    goPackage.Name,
   197  							PluginName: derivedName,
   198  							ImportName: deriveImport(x.Name.Name, derivedName),
   199  							TypeName:   x.Name.Name,
   200  							Path:       dir,
   201  						})
   202  						// The AST stops parsing when we return false. Once we
   203  						// find the symbol we want we can stop parsing.
   204  
   205  						// DEBUG:
   206  						// fmt.Printf("package %#v\n", goPackage)
   207  						return false
   208  					}
   209  				}
   210  				return true
   211  			})
   212  		}
   213  	}
   214  
   215  	return postProcessors, nil
   216  }
   217  
   218  func discoverBuilders() ([]plugin, error) {
   219  	path := "./builder"
   220  	typeID := "Builder"
   221  	return discoverTypesInPath(path, typeID)
   222  }
   223  
   224  func discoverProvisioners() ([]plugin, error) {
   225  	path := "./provisioner"
   226  	typeID := "Provisioner"
   227  	return discoverTypesInPath(path, typeID)
   228  }
   229  
   230  func discoverPostProcessors() ([]plugin, error) {
   231  	path := "./post-processor"
   232  	typeID := "PostProcessor"
   233  	return discoverTypesInPath(path, typeID)
   234  }
   235  
   236  const source = `//
   237  // This file is automatically generated by scripts/generate-plugins.go -- Do not edit!
   238  //
   239  
   240  package command
   241  
   242  import (
   243  	"fmt"
   244  	"log"
   245  	"regexp"
   246  	"strings"
   247  
   248  	"github.com/mitchellh/packer/packer"
   249  	"github.com/mitchellh/packer/packer/plugin"
   250  
   251  IMPORTS
   252  )
   253  
   254  type PluginCommand struct {
   255  	Meta
   256  }
   257  
   258  BUILDERS
   259  
   260  PROVISIONERS
   261  
   262  POSTPROCESSORS
   263  
   264  var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner)-(.+)")
   265  
   266  func (c *PluginCommand) Run(args []string) int {
   267  	// This is an internal call (users should not call this directly) so we're
   268  	// not going to do much input validation. If there's a problem we'll often
   269  	// just crash. Error handling should be added to facilitate debugging.
   270  	log.Printf("args: %#v", args)
   271  	if len(args) != 1 {
   272  		c.Ui.Error("Wrong number of args")
   273  		return 1
   274  	}
   275  
   276  	// Plugin will match something like "packer-builder-amazon-ebs"
   277  	parts := pluginRegexp.FindStringSubmatch(args[0])
   278  	if len(parts) != 3 {
   279  		c.Ui.Error(fmt.Sprintf("Error parsing plugin argument [DEBUG]: %#v", parts))
   280  		return 1
   281  	}
   282  	pluginType := parts[1] // capture group 1 (builder|post-processor|provisioner)
   283  	pluginName := parts[2] // capture group 2 (.+)
   284  
   285  	server, err := plugin.Server()
   286  	if err != nil {
   287  		c.Ui.Error(fmt.Sprintf("Error starting plugin server: %s", err))
   288  		return 1
   289  	}
   290  
   291  	switch pluginType {
   292  	case "builder":
   293  		builder, found := Builders[pluginName]
   294  		if !found {
   295  			c.Ui.Error(fmt.Sprintf("Could not load builder: %s", pluginName))
   296  			return 1
   297  		}
   298  		server.RegisterBuilder(builder)
   299  	case "provisioner":
   300  		provisioner, found := Provisioners[pluginName]
   301  		if !found {
   302  			c.Ui.Error(fmt.Sprintf("Could not load provisioner: %s", pluginName))
   303  			return 1
   304  		}
   305  		server.RegisterProvisioner(provisioner)
   306  	case "post-processor":
   307  		postProcessor, found := PostProcessors[pluginName]
   308  		if !found {
   309  			c.Ui.Error(fmt.Sprintf("Could not load post-processor: %s", pluginName))
   310  			return 1
   311  		}
   312  		server.RegisterPostProcessor(postProcessor)
   313  	}
   314  
   315  	server.Serve()
   316  
   317  	return 0
   318  }
   319  
   320  func (*PluginCommand) Help() string {
   321  	helpText := ` + "`" + `
   322  Usage: packer plugin PLUGIN
   323  
   324    Runs an internally-compiled version of a plugin from the packer binary.
   325  
   326    NOTE: this is an internal command and you should not call it yourself.
   327  ` + "`" + `
   328  
   329  	return strings.TrimSpace(helpText)
   330  }
   331  
   332  func (c *PluginCommand) Synopsis() string {
   333  	return "internal plugin command"
   334  }
   335  `