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
pull/12664/head
Wilken Rivera 2 years ago
parent e8d5436228
commit da061169bc

@ -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
}

@ -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},

@ -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()
}

Loading…
Cancel
Save