From 8232633d4ff8d40cf82a258cb50a401a0b3d3812 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 | 70 +++++++++++++++++++++++ 6 files changed, 231 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 c77687c3586..875567b0db7 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 1989027499b..17a795bec84 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 60199bdbf71..6447163bc5f 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 0d39d70d579..8d0da5ac9ec 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 c88f54f86b3..3c7222a8c74 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,12 @@ func (c *PluginConfig) DiscoverMultiPlugin(pluginName, pluginPath string) error log.Printf("found external %v datasource from %s plugin", desc.Datasources, pluginName) } + if c.PluginComponents == nil { + c.PluginComponents = map[string]PluginSpec{} + } + + c.PluginComponents[pluginName] = pluginSpec + return nil }