From efe182b221e6b5fe6c0c654b3b46dc007c87fd4f Mon Sep 17 00:00:00 2001 From: Lucas Bajolet Date: Wed, 22 Nov 2023 14:57:29 -0500 Subject: [PATCH] packer: link to docs if a component is missing When a user invokes packer for a build or validation, the template being processed needs components to be present for Packer to process it without error. If the component cannot be found from the plugins loaded (or from the components bundled with Packer), Packer errors, and the command fails. This is expected, but the error message does not suggest anything to fix the error, potantially leaving users confused at the problem. This commit suggests either a replacement (in case of a typo), or points to the web documentation for Packer, specifically the integrations, so they can look for the plugin they're missing, and install it, so subsequent invocations of Packer work. --- hcl2template/plugin.go | 59 +++++++++++++++++++++++++++++++++++++++--- packer/core.go | 53 ++++++++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/hcl2template/plugin.go b/hcl2template/plugin.go index 323263596f8..321616017b6 100644 --- a/hcl2template/plugin.go +++ b/hcl2template/plugin.go @@ -133,10 +133,22 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics { // its body. srcUsage := &(build.Sources[i]) if !cfg.parser.PluginConfig.Builders.Has(srcUsage.Type) { + detail := fmt.Sprintf( + "The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ + "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ + "https://developer.hashicorp.com/packer/integrations?filter=%s", + buildSourceLabel, + srcUsage.Type, + strings.Split(srcUsage.Type, "-")[0], + ) + + if sugg := didyoumean.NameSuggestion(srcUsage.Type, cfg.parser.PluginConfig.Builders.List()); sugg != "" { + detail = fmt.Sprintf("Did you mean to use %q?", sugg) + } diags = append(diags, &hcl.Diagnostic{ Summary: "Unknown " + buildSourceLabel + " type " + srcUsage.Type, Subject: &build.HCL2Ref.DefRange, - Detail: fmt.Sprintf("known builders: %v", cfg.parser.PluginConfig.Builders.List()), + Detail: detail, Severity: hcl.DiagError, }) continue @@ -169,10 +181,23 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics { for _, provBlock := range build.ProvisionerBlocks { if !cfg.parser.PluginConfig.Provisioners.Has(provBlock.PType) { + detail := fmt.Sprintf( + "The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ + "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ + "https://developer.hashicorp.com/packer/integrations?filter=%s", + buildProvisionerLabel, + provBlock.PType, + strings.Split(provBlock.PType, "-")[0], + ) + + if sugg := didyoumean.NameSuggestion(provBlock.PType, cfg.parser.PluginConfig.Provisioners.List()); sugg != "" { + detail = fmt.Sprintf("Did you mean to use %q?", sugg) + } + diags = append(diags, &hcl.Diagnostic{ Summary: fmt.Sprintf("Unknown "+buildProvisionerLabel+" type %q", provBlock.PType), Subject: provBlock.HCL2Ref.TypeRange.Ptr(), - Detail: fmt.Sprintf("known "+buildProvisionerLabel+"s: %v", cfg.parser.PluginConfig.Provisioners.List()), + Detail: detail, Severity: hcl.DiagError, }) } @@ -180,10 +205,23 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics { if build.ErrorCleanupProvisionerBlock != nil { if !cfg.parser.PluginConfig.Provisioners.Has(build.ErrorCleanupProvisionerBlock.PType) { + detail := fmt.Sprintf( + "The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ + "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ + "https://developer.hashicorp.com/packer/integrations?filter=%s", + buildErrorCleanupProvisionerLabel, + build.ErrorCleanupProvisionerBlock.PType, + strings.Split(build.ErrorCleanupProvisionerBlock.PType, "-")[0], + ) + + if sugg := didyoumean.NameSuggestion(build.ErrorCleanupProvisionerBlock.PType, cfg.parser.PluginConfig.Provisioners.List()); sugg != "" { + detail = fmt.Sprintf("Did you mean to use %q?", sugg) + } + diags = append(diags, &hcl.Diagnostic{ Summary: fmt.Sprintf("Unknown "+buildErrorCleanupProvisionerLabel+" type %q", build.ErrorCleanupProvisionerBlock.PType), Subject: build.ErrorCleanupProvisionerBlock.HCL2Ref.TypeRange.Ptr(), - Detail: fmt.Sprintf("known "+buildErrorCleanupProvisionerLabel+"s: %v", cfg.parser.PluginConfig.Provisioners.List()), + Detail: detail, Severity: hcl.DiagError, }) } @@ -192,10 +230,23 @@ func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics { for _, ppList := range build.PostProcessorsLists { for _, ppBlock := range ppList { if !cfg.parser.PluginConfig.PostProcessors.Has(ppBlock.PType) { + detail := fmt.Sprintf( + "The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ + "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ + "https://developer.hashicorp.com/packer/integrations?filter=%s", + buildPostProcessorLabel, + ppBlock.PType, + strings.Split(ppBlock.PType, "-")[0], + ) + + if sugg := didyoumean.NameSuggestion(ppBlock.PType, cfg.parser.PluginConfig.PostProcessors.List()); sugg != "" { + detail = fmt.Sprintf("Did you mean to use %q?", sugg) + } + diags = append(diags, &hcl.Diagnostic{ Summary: fmt.Sprintf("Unknown "+buildPostProcessorLabel+" type %q", ppBlock.PType), Subject: ppBlock.HCL2Ref.TypeRange.Ptr(), - Detail: fmt.Sprintf("known "+buildPostProcessorLabel+"s: %v", cfg.parser.PluginConfig.PostProcessors.List()), + Detail: detail, Severity: hcl.DiagError, }) } diff --git a/packer/core.go b/packer/core.go index 732f026360f..4803f1de474 100644 --- a/packer/core.go +++ b/packer/core.go @@ -18,6 +18,7 @@ import ( multierror "github.com/hashicorp/go-multierror" version "github.com/hashicorp/go-version" hcl "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/packer-plugin-sdk/didyoumean" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/template" "github.com/hashicorp/packer-plugin-sdk/template/interpolate" @@ -218,15 +219,33 @@ func (c *Core) BuildNames(only, except []string) []string { func (c *Core) generateCoreBuildProvisioner(rawP *template.Provisioner, rawName string) (CoreBuildProvisioner, error) { // Get the provisioner cbp := CoreBuildProvisioner{} + + if !c.components.PluginConfig.Provisioners.Has(rawP.Type) { + err := fmt.Errorf( + "The provisioner %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ + "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ + "https://developer.hashicorp.com/packer/integrations?filter=%s", + rawP.Type, + strings.Split(rawP.Type, "-")[0], + ) + + if sugg := didyoumean.NameSuggestion(rawP.Type, c.components.PluginConfig.Builders.List()); sugg != "" { + err = fmt.Errorf("Did you mean to use %q?", sugg) + } + + return cbp, err + } + provisioner, err := c.components.PluginConfig.Provisioners.Start(rawP.Type) if err != nil { return cbp, fmt.Errorf( "error initializing provisioner '%s': %s", rawP.Type, err) } + // Seems unlikely that a provisioner doesn't start successfully without error if provisioner == nil { return cbp, fmt.Errorf( - "provisioner type not found: %s", rawP.Type) + "provisioner failed to be started and did not error: %s", rawP.Type) } // Get the configuration @@ -335,6 +354,22 @@ func (c *Core) Build(n string) (packersdk.Build, error) { // For reference, the builtin BuilderStore is generated in // packer/config.go in the Discover() func. + if !c.components.PluginConfig.Builders.Has(configBuilder.Type) { + err := fmt.Errorf( + "The builder %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ + "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ + "https://developer.hashicorp.com/packer/integrations?filter=%s", + configBuilder.Type, + strings.Split(configBuilder.Type, "-")[0], + ) + + if sugg := didyoumean.NameSuggestion(configBuilder.Type, c.components.PluginConfig.Builders.List()); sugg != "" { + err = fmt.Errorf("Did you mean to use %q?", sugg) + } + + return nil, err + } + // the Start command launches the builder plugin of the given type without // calling Prepare() or passing any build-specific details. builder, err := c.components.PluginConfig.Builders.Start(configBuilder.Type) @@ -396,6 +431,22 @@ func (c *Core) Build(n string) (packersdk.Build, error) { break } + if !c.components.PluginConfig.PostProcessors.Has(rawP.Type) { + err := fmt.Errorf( + "The post-processor %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+ + "You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+ + "https://developer.hashicorp.com/packer/integrations?filter=%s", + rawP.Type, + strings.Split(rawP.Type, "-")[0], + ) + + if sugg := didyoumean.NameSuggestion(rawP.Type, c.components.PluginConfig.PostProcessors.List()); sugg != "" { + err = fmt.Errorf("Did you mean to use %q?", sugg) + } + + return nil, err + } + // Get the post-processor postProcessor, err := c.components.PluginConfig.PostProcessors.Start(rawP.Type) if err != nil {