From da061169bc3cf46b36f3cb55b3cec9cddc5cd22a Mon Sep 17 00:00:00 2001 From: Wilken Rivera Date: Thu, 19 Oct 2023 11:00:41 -0400 Subject: [PATCH] cmd/hcl2_upgrade: Don't error when using a HashiCorp plugin that is not installed This change updates hcl2_upgrade to not flag known plugin components, those used for generating the required plugins block, when upgrading a legacy JSON template to HCL2. Any unknown plugins will be installed after running packer init on the generated template so we don't error. We may want to suggest running packer init to install any missing plugins. * Move knownPluginPrefixes into the hcl2_upgrade command --- command/hcl2_upgrade.go | 132 +++++++++++++++++++++-------------- command/hcl2_upgrade_test.go | 24 +++---- command/meta.go | 87 ----------------------- 3 files changed, 91 insertions(+), 152 deletions(-) diff --git a/command/hcl2_upgrade.go b/command/hcl2_upgrade.go index 8295e4e8ae0..3393ee86bad 100644 --- a/command/hcl2_upgrade.go +++ b/command/hcl2_upgrade.go @@ -29,42 +29,6 @@ import ( "github.com/zclconf/go-cty/cty" ) -type HCL2UpgradeCommand struct { - Meta -} - -func (c *HCL2UpgradeCommand) Run(args []string) int { - ctx, cleanup := handleTermInterrupt(c.Ui) - defer cleanup() - - cfg, ret := c.ParseArgs(args) - if ret != 0 { - return ret - } - - return c.RunContext(ctx, cfg) -} - -func (c *HCL2UpgradeCommand) ParseArgs(args []string) (*HCL2UpgradeArgs, int) { - var cfg HCL2UpgradeArgs - flags := c.Meta.FlagSet("hcl2_upgrade") - flags.Usage = func() { c.Ui.Say(c.Help()) } - cfg.AddFlagSets(flags) - if err := flags.Parse(args); err != nil { - return &cfg, 1 - } - args = flags.Args() - if len(args) != 1 { - flags.Usage() - return &cfg, 1 - } - cfg.Path = args[0] - if cfg.OutputFile == "" { - cfg.OutputFile = cfg.Path + ".pkr.hcl" - } - return &cfg, 0 -} - const ( hcl2UpgradeFileHeader = `# This file was autogenerated by the 'packer hcl2_upgrade' command. We # recommend double checking that everything is correct before going forward. We @@ -130,6 +94,59 @@ var ( strftime = false ) +// knownPlugins represent the HashiCorp maintained plugins the we can confidently +// construct a required plugins block for. +var knownPlugins = map[string]string{ + "amazon": "github.com/hashicorp/amazon", + "ansible": "github.com/hashicorp/ansible", + "azure": "github.com/hashicorp/azure", + "docker": "github.com/hashicorp/docker", + "googlecompute": "github.com/hashicorp/googlecompute", + "qemu": "github.com/hashicorp/qemu", + "vagrant": "github.com/hashicorp/vagrant", + "vmware": "github.com/hashicorp/vmware", + "vsphere": "github.com/hashicorp/vsphere", +} + +// unknownPluginName represents any plugin not in knownPlugins or bundled into Packer +const unknownPluginName string = "unknown" + +type HCL2UpgradeCommand struct { + Meta +} + +func (c *HCL2UpgradeCommand) Run(args []string) int { + ctx, cleanup := handleTermInterrupt(c.Ui) + defer cleanup() + + cfg, ret := c.ParseArgs(args) + if ret != 0 { + return ret + } + + return c.RunContext(ctx, cfg) +} + +func (c *HCL2UpgradeCommand) ParseArgs(args []string) (*HCL2UpgradeArgs, int) { + var cfg HCL2UpgradeArgs + flags := c.Meta.FlagSet("hcl2_upgrade") + flags.Usage = func() { c.Ui.Say(c.Help()) } + cfg.AddFlagSets(flags) + if err := flags.Parse(args); err != nil { + return &cfg, 1 + } + args = flags.Args() + if len(args) != 1 { + flags.Usage() + return &cfg, 1 + } + cfg.Path = args[0] + if cfg.OutputFile == "" { + cfg.OutputFile = cfg.Path + ".pkr.hcl" + } + return &cfg, 0 +} + type BlockParser interface { Parse(*template.Template) error Write(*bytes.Buffer) @@ -169,7 +186,6 @@ func (c *HCL2UpgradeCommand) RunContext(_ context.Context, cla *HCL2UpgradeArgs) tpl := core.Template // Parse blocks - packerBlock := &PackerParser{ WithAnnotations: cla.WithAnnotations, } @@ -827,28 +843,28 @@ func gatherPluginsFromTemplate(tpl *template.Template) []string { plugins := map[string]struct{}{} for _, b := range tpl.Builders { - for prefix, plugin := range knownPluginPrefixes { - if strings.HasPrefix(b.Type, prefix) { - plugins[plugin] = struct{}{} - } + name := knownPluginComponent(b.Type) + if name == unknownPluginName { + continue } + plugins[knownPlugins[name]] = struct{}{} } for _, p := range tpl.Provisioners { - for prefix, plugin := range knownPluginPrefixes { - if strings.HasPrefix(p.Type, prefix) { - plugins[plugin] = struct{}{} - } + name := knownPluginComponent(p.Type) + if name == unknownPluginName { + continue } + plugins[knownPlugins[name]] = struct{}{} } for _, pps := range tpl.PostProcessors { for _, pp := range pps { - for prefix, plugin := range knownPluginPrefixes { - if strings.HasPrefix(pp.Type, prefix) { - plugins[plugin] = struct{}{} - } + name := knownPluginComponent(pp.Type) + if name == unknownPluginName { + continue } + plugins[knownPlugins[name]] = struct{}{} } } @@ -1182,18 +1198,17 @@ type SourceParser struct { } func (p *SourceParser) Parse(tpl *template.Template) error { - var unknownBuilders []string if p.out == nil { p.out = []byte{} } + + var unknownBuilders []string for i, builderCfg := range p.Builders { sourcesContent := hclwrite.NewEmptyFile() body := sourcesContent.Body() - body.AppendNewline() - if !p.BuilderPlugins.Has(builderCfg.Type) { + if !p.BuilderPlugins.Has(builderCfg.Type) && knownPluginComponent(builderCfg.Type) == unknownPluginName { unknownBuilders = append(unknownBuilders, builderCfg.Type) - } if builderCfg.Name == "" || builderCfg.Name == builderCfg.Type { builderCfg.Name = fmt.Sprintf("autogenerated_%d", i+1) @@ -1206,9 +1221,11 @@ func (p *SourceParser) Parse(tpl *template.Template) error { p.out = append(p.out, transposeTemplatingCalls(sourcesContent.Bytes())...) } + // TODO update to output to stderr as opposed to having the command exit 1 if len(unknownBuilders) > 0 { return fmt.Errorf("unknown builder type(s): %v\n", unknownBuilders) } + return nil } @@ -1412,3 +1429,12 @@ func fixQuoting(old string) string { return string(body) } + +func knownPluginComponent(component string) string { + for prefix := range knownPlugins { + if strings.HasPrefix(component, prefix) { + return prefix + } + } + return unknownPluginName +} diff --git a/command/hcl2_upgrade_test.go b/command/hcl2_upgrade_test.go index 487d99999cb..9a3833f909e 100644 --- a/command/hcl2_upgrade_test.go +++ b/command/hcl2_upgrade_test.go @@ -19,22 +19,22 @@ func Test_hcl2_upgrade(t *testing.T) { exitCode int exitEarly bool }{ - {folder: "unknown_builder", flags: []string{}, exitCode: 1}, - {folder: "complete", flags: []string{"-with-annotations"}}, - {folder: "without-annotations", flags: []string{}}, - {folder: "minimal", flags: []string{"-with-annotations"}}, - {folder: "source-name", flags: []string{"-with-annotations"}}, - {folder: "error-cleanup-provisioner", flags: []string{"-with-annotations"}}, - {folder: "aws-access-config", flags: []string{}}, - {folder: "escaping", flags: []string{}}, - {folder: "vsphere_linux_options_and_network_interface", exitCode: 1, flags: []string{}}, + {folder: "unknown_builder", flags: []string{}, exitCode: 1}, // warn for unknown components not tracked in knownPluginPrefixes + {folder: "complete", flags: []string{"-with-annotations"}, exitCode: 0}, + {folder: "without-annotations", flags: []string{}, exitCode: 0}, + {folder: "minimal", flags: []string{"-with-annotations"}, exitCode: 0}, + {folder: "source-name", flags: []string{"-with-annotations"}, exitCode: 0}, + {folder: "error-cleanup-provisioner", flags: []string{"-with-annotations"}, exitCode: 0}, + {folder: "aws-access-config", flags: []string{}, exitCode: 0}, + {folder: "escaping", flags: []string{}, exitCode: 0}, + {folder: "vsphere_linux_options_and_network_interface", flags: []string{}, exitCode: 0}, //do not warn for known uninstalled plugins components {folder: "nonexistent", flags: []string{}, exitCode: 1, exitEarly: true}, {folder: "placeholders", flags: []string{}, exitCode: 0}, {folder: "ami_test", flags: []string{}, exitCode: 0}, {folder: "azure_shg", flags: []string{}, exitCode: 0}, - {folder: "variables-only", flags: []string{}}, - {folder: "variables-with-variables", flags: []string{}}, - {folder: "complete-variables-with-template-engine", flags: []string{}}, + {folder: "variables-only", flags: []string{}, exitCode: 0}, + {folder: "variables-with-variables", flags: []string{}, exitCode: 0}, + {folder: "complete-variables-with-template-engine", flags: []string{}, exitCode: 0}, {folder: "undeclared-variables", flags: []string{}, exitCode: 0}, {folder: "varfile-with-no-variables-block", flags: []string{}, exitCode: 0}, {folder: "bundled-plugin-used", flags: []string{}, exitCode: 0}, diff --git a/command/meta.go b/command/meta.go index 0e464ccc36d..9807df78b5d 100644 --- a/command/meta.go +++ b/command/meta.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "os" - "strings" "github.com/hashicorp/hcl/v2/hclparse" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" @@ -167,89 +166,3 @@ func (m *Meta) GetConfigFromJSON(cla *MetaArgs) (packer.Handler, int) { } return core, ret } - -var knownPluginPrefixes = map[string]string{ - "amazon": "github.com/hashicorp/amazon", - "ansible": "github.com/hashicorp/ansible", - "azure": "github.com/hashicorp/azure", - "docker": "github.com/hashicorp/docker", - "googlecompute": "github.com/hashicorp/googlecompute", - "qemu": "github.com/hashicorp/qemu", - "vagrant": "github.com/hashicorp/vagrant", - "vmware": "github.com/hashicorp/vmware", - "vsphere": "github.com/hashicorp/vsphere", -} - -func (m *Meta) fixRequiredPlugins(config *hcl2template.PackerConfig) string { - plugins := map[string]struct{}{} - - for _, b := range config.Builds { - for _, b := range b.Sources { - for prefix, plugin := range knownPluginPrefixes { - if strings.HasPrefix(b.Type, prefix) { - plugins[plugin] = struct{}{} - } - } - } - - for _, p := range b.ProvisionerBlocks { - for prefix, plugin := range knownPluginPrefixes { - if strings.HasPrefix(p.PType, prefix) { - plugins[plugin] = struct{}{} - } - } - } - - for _, pps := range b.PostProcessorsLists { - for _, pp := range pps { - for prefix, plugin := range knownPluginPrefixes { - if strings.HasPrefix(pp.PType, prefix) { - plugins[plugin] = struct{}{} - } - } - } - } - } - - for _, ds := range config.Datasources { - for prefix, plugin := range knownPluginPrefixes { - if strings.HasPrefix(ds.Type, prefix) { - plugins[plugin] = struct{}{} - } - } - } - - retPlugins := make([]string, 0, len(plugins)) - for plugin := range plugins { - retPlugins = append(retPlugins, plugin) - } - - return generateRequiredPluginsBlock(retPlugins) -} - -func generateRequiredPluginsBlock(plugins []string) string { - if len(plugins) == 0 { - return "" - } - - buf := &strings.Builder{} - buf.WriteString(` -packer { - required_plugins {`) - - for _, plugin := range plugins { - pluginName := strings.Replace(plugin, "github.com/hashicorp/", "", 1) - fmt.Fprintf(buf, ` - %s = { - source = %q - version = "~> 1" - }`, pluginName, plugin) - } - - buf.WriteString(` - } -} -`) - - return buf.String() -}