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 `