Skip to content

Commit

Permalink
cmd/hcl2_upgrade: Don't error when using a HashiCorp plugin that is n…
Browse files Browse the repository at this point in the history
…ot 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
  • Loading branch information
nywilken committed Oct 23, 2023
1 parent 31a6357 commit 33f20a7
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 152 deletions.
132 changes: 79 additions & 53 deletions command/hcl2_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -169,7 +186,6 @@ func (c *HCL2UpgradeCommand) RunContext(_ context.Context, cla *HCL2UpgradeArgs)
tpl := core.Template

// Parse blocks

packerBlock := &PackerParser{
WithAnnotations: cla.WithAnnotations,
}
Expand Down Expand Up @@ -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{}{}
}
}

Expand Down Expand Up @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
24 changes: 12 additions & 12 deletions command/hcl2_upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
87 changes: 0 additions & 87 deletions command/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"fmt"
"io"
"os"
"strings"

"github.com/hashicorp/hcl/v2/hclparse"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
Expand Down Expand Up @@ -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()
}

0 comments on commit 33f20a7

Please sign in to comment.