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