From ad5a2d590b363baf01fe9df3f806c9d06f983f90 Mon Sep 17 00:00:00 2001 From: Lucas Bajolet Date: Thu, 10 Aug 2023 10:08:55 -0400 Subject: [PATCH] packer: log plugins used and their version/path As maintainers of Packer and plugins, we ask our users to provide us with the versions of Packer and the plugins they use when they open an issue for us to review. This is often misunderstood, and may be hidden in the logs, making it harder for all of us to understand where to look. This commit adds a log statement that reports the list of plugins used, their version, and the path they've been loaded from. --- bundled_plugin_versions.go | 40 +++++++++++++ command/build.go | 2 + command/meta.go | 113 ++++++++++++++++++++++++++++++++++++- command/validate.go | 2 + main.go | 7 ++- packer/plugin.go | 66 ++++++++++++++++++++++ 6 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 bundled_plugin_versions.go 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 }