diff --git a/bundled_plugin_versions.go b/bundled_plugin_versions.go new file mode 100644 index 00000000000..ee45543faa3 --- /dev/null +++ b/bundled_plugin_versions.go @@ -0,0 +1,40 @@ +package main + +import ( + _ "embed" + "fmt" + "os" + "regexp" + "strings" + + "github.com/hashicorp/packer/packer" + "golang.org/x/mod/modfile" +) + +//go:embed go.mod +var mod string + +var pluginRegex = regexp.MustCompile("packer-plugin-.*$") + +func GetBundledPluginVersions() map[string]packer.PluginSpec { + pluginSpecs := map[string]packer.PluginSpec{} + + mods, err := modfile.Parse("", []byte(mod), nil) + if err != nil { + panic(fmt.Sprintf("failed to parse embedded modfile: %s", err)) + } + + for _, req := range mods.Require { + if pluginRegex.MatchString(req.Mod.Path) { + pluginName := pluginRegex.FindString(req.Mod.Path) + pluginShortName := strings.Replace(pluginName, "packer-plugin-", "", 1) + pluginSpecs[pluginShortName] = packer.PluginSpec{ + Name: pluginShortName, + Version: fmt.Sprintf("bundled (%s)", req.Mod.Version), + Path: os.Args[0], + } + } + } + + return pluginSpecs +} diff --git a/command/build.go b/command/build.go index 842938adcb9..3ca76d39bd1 100644 --- a/command/build.go +++ b/command/build.go @@ -101,6 +101,8 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int return ret } + c.LogPluginUsage(packerStarter) + hcpRegistry, diags := registry.New(packerStarter, c.Ui) ret = writeDiags(c.Ui, nil, diags) if ret != 0 { diff --git a/command/meta.go b/command/meta.go index fb3d9efb2a7..de47e262233 100644 --- a/command/meta.go +++ b/command/meta.go @@ -8,7 +8,9 @@ import ( "flag" "fmt" "io" + "log" "os" + "sort" "strings" "github.com/hashicorp/hcl/v2" @@ -257,7 +259,9 @@ func (m *Meta) detectBundledPluginsJSON(core *packer.Core) []string { } } - return compileBundledPluginList(bundledPlugins) + bundledPluginList := compileBundledPluginList(bundledPlugins) + + return bundledPluginList } var knownPluginPrefixes = map[string]string{ @@ -354,5 +358,110 @@ func (m *Meta) detectBundledPluginsHCL2(config *hcl2template.PackerConfig) []str } } - return compileBundledPluginList(bundledPlugins) + bundledPluginList := compileBundledPluginList(bundledPlugins) + + return bundledPluginList +} + +func (m *Meta) LogPluginUsage(handler packer.Handler) { + switch h := handler.(type) { + case *packer.Core: + m.logPluginUsageJSON(h) + case *hcl2template.PackerConfig: + m.logPluginUsageHCL2(handler.(*hcl2template.PackerConfig)) + } +} + +func (m *Meta) logPluginUsageJSON(c *packer.Core) { + usedPlugins := map[string]packer.PluginSpec{} + + tmpl := c.Template + if tmpl == nil { + panic("No template parsed. This is a Packer bug which should be reported, please open an issue on the project's issue tracker.") + } + + for _, b := range tmpl.Builders { + // Check since internal components are not registered as components + ps, ok := m.CoreConfig.Components.PluginConfig.GetSpecForPlugin(b.Type) + if ok { + usedPlugins[ps.Name] = ps + } + } + + for _, p := range tmpl.Provisioners { + // Check since internal components are not registered as components + ps, ok := m.CoreConfig.Components.PluginConfig.GetSpecForPlugin(p.Type) + if ok { + usedPlugins[ps.Name] = ps + } + } + + for _, pps := range tmpl.PostProcessors { + for _, pp := range pps { + // Check since internal components are not registered as components + ps, ok := m.CoreConfig.Components.PluginConfig.GetSpecForPlugin(pp.Type) + if ok { + usedPlugins[ps.Name] = ps + } + } + } + + logPlugins(usedPlugins) +} + +func (m *Meta) logPluginUsageHCL2(config *hcl2template.PackerConfig) { + usedPlugins := map[string]packer.PluginSpec{} + + for _, b := range config.Builds { + for _, src := range b.Sources { + ps, ok := m.CoreConfig.Components.PluginConfig.GetSpecForPlugin(src.Type) + if ok { + usedPlugins[ps.Name] = ps + } + } + + for _, p := range b.ProvisionerBlocks { + ps, ok := m.CoreConfig.Components.PluginConfig.GetSpecForPlugin(p.PType) + if ok { + usedPlugins[ps.Name] = ps + } + } + + for _, pps := range b.PostProcessorsLists { + for _, pp := range pps { + ps, ok := m.CoreConfig.Components.PluginConfig.GetSpecForPlugin(pp.PType) + if ok { + usedPlugins[ps.Name] = ps + } + } + } + } + + for _, ds := range config.Datasources { + ps, ok := m.CoreConfig.Components.PluginConfig.GetSpecForPlugin(ds.Type) + if ok { + usedPlugins[ps.Name] = ps + } + } + + logPlugins(usedPlugins) +} + +func logPlugins(usedPlugins map[string]packer.PluginSpec) { + // Could happen if no plugin is loaded and we only rely on internal components + if len(usedPlugins) == 0 { + return + } + + pluginNames := make([]string, 0, len(usedPlugins)) + for pn := range usedPlugins { + pluginNames = append(pluginNames, pn) + } + sort.Strings(pluginNames) + + log.Printf("[INFO] - Used plugins") + for _, pn := range pluginNames { + ps := usedPlugins[pn] + log.Printf(fmt.Sprintf("*\t%s - %s: %s", ps.Name, ps.Version, ps.Path)) + } } diff --git a/command/validate.go b/command/validate.go index 2b7e2107b0d..a402ec4e4e0 100644 --- a/command/validate.go +++ b/command/validate.go @@ -81,6 +81,8 @@ func (c *ValidateCommand) RunContext(ctx context.Context, cla *ValidateArgs) int return ret } + c.LogPluginUsage(packerStarter) + _, diags = packerStarter.GetBuilds(packer.GetBuildsOptions{ Only: cla.Only, Except: cla.Except, diff --git a/main.go b/main.go index 728f56860af..f3f62adba32 100644 --- a/main.go +++ b/main.go @@ -325,7 +325,6 @@ func loadConfig() (*config, error) { PluginMinPort: 10000, PluginMaxPort: 25000, KnownPluginFolders: packer.PluginFolders("."), - // BuilderRedirects BuilderRedirects: map[string]string{ @@ -391,7 +390,13 @@ func loadConfig() (*config, error) { //"vsphere": "github.com/hashicorp/vsphere", //"vsphere-template": "github.com/hashicorp/vsphere", }, + PluginComponents: map[string]packer.PluginSpec{}, + } + bundledPlugins := GetBundledPluginVersions() + for pn, ps := range bundledPlugins { + config.Plugins.PluginComponents[pn] = ps } + if err := config.Plugins.Discover(); err != nil { return nil, err } diff --git a/packer/plugin.go b/packer/plugin.go index dd3e6d31719..0cd064e68b9 100644 --- a/packer/plugin.go +++ b/packer/plugin.go @@ -25,6 +25,20 @@ var defaultChecksummer = plugingetter.Checksummer{ Hash: sha256.New(), } +type PluginSpec struct { + Name string + Path string + Version string +} + +func (ps PluginSpec) String() string { + return fmt.Sprintf(` +Plugin: %s +Path: %s +Version: %s`, + ps.Name, ps.Path, ps.Version) +} + // PluginConfig helps load and use packer plugins type PluginConfig struct { KnownPluginFolders []string @@ -51,6 +65,50 @@ type PluginConfig struct { DatasourceRedirects map[string]string ProvisionerRedirects map[string]string PostProcessorRedirects map[string]string + PluginComponents map[string]PluginSpec +} + +// GetSpecForComponent returns a pluginspec if the component requested exists in the loaded plugin specs +// +// If it does not, the returned boolean will be false. +func (c PluginConfig) GetSpecForPlugin(name string) (PluginSpec, bool) { + names := getPartsFromComponent(name) + + for _, name := range names { + pc, ok := c.PluginComponents[name] + if ok { + return pc, true + } + } + + return PluginSpec{}, false +} + +// getPartsFromComponent splits the plugin on '-' and returns a list of potential names +// starting from the longest, and ending on the shortest. +// +// Ex: +// ```go +// +// getPartsFromComponent("plugin-name-component-name") => [ +// "plugin-name-component-name", +// "plugin-name-component", +// "plugin-name", +// "plugin", +// ] +// +// ``` +func getPartsFromComponent(name string) []string { + rets := []string{} + + parts := strings.Split(name, "-") + + for len(parts) > 0 { + rets = append(rets, strings.Join(parts, "-")) + parts = parts[:len(parts)-1] + } + + return rets } // PACKERSPACE is used to represent the spaces that separate args for a command @@ -280,6 +338,12 @@ func (c *PluginConfig) DiscoverMultiPlugin(pluginName, pluginPath string) error return err } + pluginSpec := PluginSpec{ + Name: pluginName, + Path: pluginPath, + Version: desc.Version, + } + pluginPrefix := pluginName + "-" for _, builderName := range desc.Builders { @@ -340,6 +404,8 @@ func (c *PluginConfig) DiscoverMultiPlugin(pluginName, pluginPath string) error log.Printf("found external %v datasource from %s plugin", desc.Datasources, pluginName) } + c.PluginComponents[pluginName] = pluginSpec + return nil }