From 0e3fcb589b98776a011fcfec9719fb66e9aba763 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Wed, 24 Mar 2021 11:31:39 +0100 Subject: [PATCH] Implicit required_plugin blocks (#10732) * used components that don't have a required_plugin block will make Packer 'implicitly' require those. These components are manually selected and commented for now. * add tests * docs --- command/init.go | 46 ++- hcl2template/common_test.go | 12 +- hcl2template/components.go | 11 + hcl2template/plugin.go | 1 + hcl2template/types.datasource.go | 12 +- hcl2template/types.packer_config_test.go | 2 +- hcl2template/types.required_plugins.go | 127 ++++++- hcl2template/types.required_plugins_test.go | 363 ++++++++++++++++++++ main.go | 61 ++++ packer/plugin-getter/plugins.go | 3 + packer/plugin.go | 17 + website/content/docs/commands/init.mdx | 15 +- 12 files changed, 652 insertions(+), 18 deletions(-) create mode 100644 hcl2template/components.go create mode 100644 hcl2template/types.required_plugins_test.go diff --git a/command/init.go b/command/init.go index eab38b648..dd49c500f 100644 --- a/command/init.go +++ b/command/init.go @@ -121,12 +121,54 @@ func (c *InitCommand) RunContext(buildCtx context.Context, cla *InitArgs) int { Getters: getters, }) if err != nil { - c.Ui.Error(err.Error()) - ret = 1 + if pluginRequirement.Implicit { + msg := fmt.Sprintf(` +Warning! At least one component used in your config file(s) has moved out of +Packer into the %q plugin. +For that reason, Packer init tried to install the latest version of the %s +plugin. Unfortunately, this failed : +%s`, + pluginRequirement.Identifier, + pluginRequirement.Identifier.Type, + err) + c.Ui.Say(msg) + } else { + c.Ui.Error(err.Error()) + ret = 1 + } } if newInstall != nil { + if pluginRequirement.Implicit { + msg := fmt.Sprintf("Installed implicitly required plugin %s %s in %q", pluginRequirement.Identifier, newInstall.Version, newInstall.BinaryPath) + ui.Say(msg) + + warn := fmt.Sprintf(` +Warning, at least one component used in your config file(s) has moved out of +Packer into the %[2]q plugin and is now being implicitly required. +For more details on implicitly required plugins see https://packer.io/docs/commands/init#implicit-required-plugin + +To avoid any backward incompatible changes with your +config file you may want to lock the plugin version by pasting the following to your config: + +packer { + required_plugins { + %[1]s = { + source = "%[2]s" + version = "~> %[3]s" + } + } +} +`, + pluginRequirement.Identifier.Type, + pluginRequirement.Identifier, + newInstall.Version, + ) + ui.Error(warn) + continue + } msg := fmt.Sprintf("Installed plugin %s %s in %q", pluginRequirement.Identifier, newInstall.Version, newInstall.BinaryPath) ui.Say(msg) + } } return ret diff --git a/hcl2template/common_test.go b/hcl2template/common_test.go index fa50013a3..c9f7b9789 100644 --- a/hcl2template/common_test.go +++ b/hcl2template/common_test.go @@ -20,8 +20,8 @@ import ( const lockedVersion = "v1.5.0" -func getBasicParser() *Parser { - return &Parser{ +func getBasicParser(opts ...getParserOption) *Parser { + parser := &Parser{ CorePackerVersion: version.Must(version.NewSemver(lockedVersion)), CorePackerVersionString: lockedVersion, Parser: hclparse.NewParser(), @@ -44,8 +44,14 @@ func getBasicParser() *Parser { }, }, } + for _, configure := range opts { + configure(parser) + } + return parser } +type getParserOption func(*Parser) + type parseTestArgs struct { filename string vars map[string]string @@ -338,7 +344,7 @@ var cmpOpts = []cmp.Option{ PackerConfig{}, Variable{}, SourceBlock{}, - Datasource{}, + DatasourceBlock{}, ProvisionerBlock{}, PostProcessorBlock{}, packer.CoreBuild{}, diff --git a/hcl2template/components.go b/hcl2template/components.go new file mode 100644 index 000000000..b2063cd38 --- /dev/null +++ b/hcl2template/components.go @@ -0,0 +1,11 @@ +package hcl2template + +// ComponentKind helps enumerate what kind of components exist in this Package. +type ComponentKind int + +const ( + Builder ComponentKind = iota + Provisioner + PostProcessor + Datasource +) diff --git a/hcl2template/plugin.go b/hcl2template/plugin.go index b43abdeb5..7f5a5206d 100644 --- a/hcl2template/plugin.go +++ b/hcl2template/plugin.go @@ -41,6 +41,7 @@ func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Di Accessor: name, Identifier: block.Type, VersionConstraints: block.Requirement.Required, + Implicit: block.PluginDependencyReason == PluginDependencyImplicit, }) uniq[name] = block } diff --git a/hcl2template/types.datasource.go b/hcl2template/types.datasource.go index c25ab5b1a..568a0882f 100644 --- a/hcl2template/types.datasource.go +++ b/hcl2template/types.datasource.go @@ -11,8 +11,8 @@ import ( "github.com/zclconf/go-cty/cty" ) -// DataBlock references an HCL 'data' block. -type Datasource struct { +// DatasourceBlock references an HCL 'data' block. +type DatasourceBlock struct { Type string Name string @@ -25,9 +25,9 @@ type DatasourceRef struct { Name string } -type Datasources map[DatasourceRef]Datasource +type Datasources map[DatasourceRef]DatasourceBlock -func (data *Datasource) Ref() DatasourceRef { +func (data *DatasourceBlock) Ref() DatasourceRef { return DatasourceRef{ Type: data.Type, Name: data.Name, @@ -124,9 +124,9 @@ func (cfg *PackerConfig) startDatasource(dataSourceStore packer.DatasourceStore, return datasource, diags } -func (p *Parser) decodeDataBlock(block *hcl.Block) (*Datasource, hcl.Diagnostics) { +func (p *Parser) decodeDataBlock(block *hcl.Block) (*DatasourceBlock, hcl.Diagnostics) { var diags hcl.Diagnostics - r := &Datasource{ + r := &DatasourceBlock{ Type: block.Labels[0], Name: block.Labels[1], block: block, diff --git a/hcl2template/types.packer_config_test.go b/hcl2template/types.packer_config_test.go index 5a522b4d0..a045ebb68 100644 --- a/hcl2template/types.packer_config_test.go +++ b/hcl2template/types.packer_config_test.go @@ -131,7 +131,7 @@ func TestParser_complete(t *testing.T) { }, }, Datasources: Datasources{ - DatasourceRef{Type: "amazon-ami", Name: "test"}: Datasource{ + DatasourceRef{Type: "amazon-ami", Name: "test"}: DatasourceBlock{ Type: "amazon-ami", Name: "test", value: cty.StringVal("foo"), diff --git a/hcl2template/types.required_plugins.go b/hcl2template/types.required_plugins.go index 736aeb2c1..f1309f09d 100644 --- a/hcl2template/types.required_plugins.go +++ b/hcl2template/types.required_plugins.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/packer/hcl2template/addrs" + "github.com/hashicorp/packer/packer" "github.com/zclconf/go-cty/cty" ) @@ -41,9 +42,13 @@ func (cfg *PackerConfig) decodeRequiredPluginsBlock(f *hcl.File) hcl.Diagnostics } func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Diagnostics { - // when a plugin is used but not defined in the required plugin blocks, it - // is 'implicitly used'. Here we read common configuration blocks to try to - // guess plugins. + // when a plugin is used but not available it should be 'implicitly + // required'. Here we read common configuration blocks to try to guess + // plugin usages. + + // decodeRequiredPluginsBlock needs to be called before + // decodeImplicitRequiredPluginsBlocks; otherwise all required plugins will + // be implicitly required too. var diags hcl.Diagnostics @@ -51,14 +56,112 @@ func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Di diags = append(diags, moreDiags...) for _, block := range content.Blocks { + switch block.Type { case sourceLabel: - // TODO + diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(Builder, block)...) + case dataSourceLabel: + diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(Datasource, block)...) + case buildLabel: + content, _, moreDiags := block.Body.PartialContent(buildSchema) + diags = append(diags, moreDiags...) + for _, block := range content.Blocks { + + switch block.Type { + case buildProvisionerLabel: + diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(Provisioner, block)...) + case buildPostProcessorLabel: + diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(PostProcessor, block)...) + case buildPostProcessorsLabel: + content, _, moreDiags := block.Body.PartialContent(postProcessorsSchema) + diags = append(diags, moreDiags...) + for _, block := range content.Blocks { + + switch block.Type { + case buildPostProcessorLabel: + diags = append(diags, cfg.decodeImplicitRequiredPluginsBlock(PostProcessor, block)...) + } + } + } + } + } } return diags } +func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlock(k ComponentKind, block *hcl.Block) hcl.Diagnostics { + if len(block.Labels) == 0 { + // malformed block ? Let's not panic :) + return nil + } + // Currently all block types are `type "component-kind" ["name"] {` + // this makes this simple. + componentName := block.Labels[0] + + store := map[ComponentKind]packer.BasicStore{ + Builder: cfg.parser.PluginConfig.Builders, + PostProcessor: cfg.parser.PluginConfig.PostProcessors, + Provisioner: cfg.parser.PluginConfig.Provisioners, + Datasource: cfg.parser.PluginConfig.DataSources, + }[k] + if store.Has(componentName) { + // If any core or pre-loaded plugin defines the `happycloud-uploader` + // pp, skip. This happens for core and manually installed plugins, as + // they will be listed in the PluginConfig before parsing any HCL. + return nil + } + + redirect := map[ComponentKind]map[string]string{ + Builder: cfg.parser.PluginConfig.BuilderRedirects, + PostProcessor: cfg.parser.PluginConfig.PostProcessorRedirects, + Provisioner: cfg.parser.PluginConfig.ProvisionerRedirects, + Datasource: cfg.parser.PluginConfig.DatasourceRedirects, + }[k][componentName] + + if redirect == "" { + // no known redirect for this component + return nil + } + + redirectAddr, diags := addrs.ParsePluginSourceString(redirect) + if diags.HasErrors() { + // This should never happen, since the map is manually filled. + return diags + } + + for _, req := range cfg.Packer.RequiredPlugins { + if _, found := req.RequiredPlugins[redirectAddr.Type]; found { + // This could happen if a plugin was forked. For example, I forked + // the github.com/hashicorp/happycloud plugin into + // github.com/azr/happycloud that is required in my config file; and + // am using the `happycloud-uploader` pp component from it. In that + // case - and to avoid miss-requires - we won't implicitly import + // any other `happycloud` plugin. + return nil + } + } + + cfg.implicitlyRequirePlugin(redirectAddr) + return nil +} + +func (cfg *PackerConfig) implicitlyRequirePlugin(plugin *addrs.Plugin) { + cfg.Packer.RequiredPlugins = append(cfg.Packer.RequiredPlugins, &RequiredPlugins{ + RequiredPlugins: map[string]*RequiredPlugin{ + plugin.Type: { + Name: plugin.Type, + Source: plugin.String(), + Type: plugin, + Requirement: VersionConstraint{ + Required: nil, // means latest + }, + PluginDependencyReason: PluginDependencyImplicit, + }, + }, + }) +} + // RequiredPlugin represents a declaration of a dependency on a particular // Plugin version or source. type RequiredPlugin struct { @@ -71,8 +174,24 @@ type RequiredPlugin struct { Type *addrs.Plugin Requirement VersionConstraint DeclRange hcl.Range + PluginDependencyReason } +// PluginDependencyReason is an enumeration of reasons why a dependency might be +// present. +type PluginDependencyReason int + +const ( + // PluginDependencyExplicit means that there is an explicit + // "required_plugin" block in the configuration. + PluginDependencyExplicit PluginDependencyReason = iota + + // PluginDependencyImplicit means that there is no explicit + // "required_plugin" block but there is at least one resource that uses this + // plugin. + PluginDependencyImplicit +) + type RequiredPlugins struct { RequiredPlugins map[string]*RequiredPlugin DeclRange hcl.Range diff --git a/hcl2template/types.required_plugins_test.go b/hcl2template/types.required_plugins_test.go new file mode 100644 index 000000000..fe0869198 --- /dev/null +++ b/hcl2template/types.required_plugins_test.go @@ -0,0 +1,363 @@ +package hcl2template + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/go-version" + "github.com/hashicorp/packer/hcl2template/addrs" +) + +func TestPackerConfig_required_plugin_parse(t *testing.T) { + + tests := []struct { + name string + cfg PackerConfig + requirePlugins string + restOfTemplate string + wantDiags bool + wantConfig PackerConfig + }{ + {"required_plugin", PackerConfig{parser: getBasicParser()}, ` + packer { + required_plugins { + amazon = { + source = "github.com/hashicorp/amazon" + version = "~> v1.2.3" + } + } + } `, ` + source "amazon-ebs" "example" { + } + `, false, PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: []*RequiredPlugins{ + {RequiredPlugins: map[string]*RequiredPlugin{ + "amazon": { + Name: "amazon", + Source: "github.com/hashicorp/amazon", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "amazon"}, + Requirement: VersionConstraint{ + Required: mustVersionConstraints(version.NewConstraint("~> v1.2.3")), + }, + PluginDependencyReason: PluginDependencyExplicit, + }, + }}, + }, + }, + }}, + {"required_plugin_forked_no_redirect", PackerConfig{parser: getBasicParser()}, ` + packer { + required_plugins { + amazon = { + source = "github.com/azr/amazon" + version = "~> v1.2.3" + } + } + } `, ` + source "amazon-chroot" "example" { + } + `, false, PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: []*RequiredPlugins{ + {RequiredPlugins: map[string]*RequiredPlugin{ + "amazon": { + Name: "amazon", + Source: "github.com/azr/amazon", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "azr", Type: "amazon"}, + Requirement: VersionConstraint{ + Required: mustVersionConstraints(version.NewConstraint("~> v1.2.3")), + }, + PluginDependencyReason: PluginDependencyExplicit, + }, + }}, + }, + }, + }}, + {"required_plugin_forked", PackerConfig{ + parser: getBasicParser(func(p *Parser) { + p.PluginConfig.BuilderRedirects = map[string]string{ + "amazon-chroot": "github.com/hashicorp/amazon", + } + }, + )}, ` + packer { + required_plugins { + amazon = { + source = "github.com/azr/amazon" + version = "~> v1.2.3" + } + } + } `, ` + source "amazon-chroot" "example" { + } + `, false, PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: []*RequiredPlugins{ + {RequiredPlugins: map[string]*RequiredPlugin{ + "amazon": { + Name: "amazon", + Source: "github.com/azr/amazon", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "azr", Type: "amazon"}, + Requirement: VersionConstraint{ + Required: mustVersionConstraints(version.NewConstraint("~> v1.2.3")), + }, + PluginDependencyReason: PluginDependencyExplicit, + }, + }}, + }, + }, + }}, + {"missing-required-plugin-for-pre-defined-builder", PackerConfig{ + parser: getBasicParser(func(p *Parser) { + p.PluginConfig.BuilderRedirects = map[string]string{ + "amazon-ebs": "github.com/hashicorp/amazon", + } + }, + )}, + ` + packer { + }`, ` + # amazon-ebs is mocked in getBasicParser() + source "amazon-ebs" "example" { + } + `, + false, + PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: nil, + }, + }}, + {"missing-required-plugin-for-builder", PackerConfig{ + parser: getBasicParser(func(p *Parser) { + p.PluginConfig.BuilderRedirects = map[string]string{ + "amazon-chroot": "github.com/hashicorp/amazon", + } + }, + )}, + ` + packer { + }`, ` + source "amazon-chroot" "example" { + } + `, + false, + PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: []*RequiredPlugins{ + {RequiredPlugins: map[string]*RequiredPlugin{ + "amazon": { + Name: "amazon", + Source: "github.com/hashicorp/amazon", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "amazon"}, + Requirement: VersionConstraint{ + Required: nil, + }, + PluginDependencyReason: PluginDependencyImplicit, + }, + }}, + }, + }, + }}, + {"missing-required-plugin-for-provisioner", PackerConfig{ + parser: getBasicParser(func(p *Parser) { + p.PluginConfig.ProvisionerRedirects = map[string]string{ + "ansible-local": "github.com/ansible/ansible", + } + }, + )}, + ` + packer { + }`, ` + build { + provisioner "ansible-local" {} + } + `, + false, + PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: []*RequiredPlugins{ + {RequiredPlugins: map[string]*RequiredPlugin{ + "ansible": { + Name: "ansible", + Source: "github.com/ansible/ansible", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "ansible", Type: "ansible"}, + Requirement: VersionConstraint{ + Required: nil, + }, + PluginDependencyReason: PluginDependencyImplicit, + }, + }}, + }, + }, + }}, + {"missing-required-plugin-for-post-processor", PackerConfig{ + parser: getBasicParser(func(p *Parser) { + p.PluginConfig.PostProcessorRedirects = map[string]string{ + "docker-push": "github.com/hashicorp/docker", + } + }, + )}, + ` + packer { + }`, ` + build { + post-processor "docker-push" {} + } + `, + false, + PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: []*RequiredPlugins{ + {RequiredPlugins: map[string]*RequiredPlugin{ + "docker": { + Name: "docker", + Source: "github.com/hashicorp/docker", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "docker"}, + Requirement: VersionConstraint{ + Required: nil, + }, + PluginDependencyReason: PluginDependencyImplicit, + }, + }}, + }, + }, + }}, + {"missing-required-plugin-for-nested-post-processor", PackerConfig{ + parser: getBasicParser(func(p *Parser) { + p.PluginConfig.PostProcessorRedirects = map[string]string{ + "docker-push": "github.com/hashicorp/docker", + } + }, + )}, + ` + packer { + }`, ` + build { + post-processors { + post-processor "docker-push" { + } + } + } + `, + false, + PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: []*RequiredPlugins{ + {RequiredPlugins: map[string]*RequiredPlugin{ + "docker": { + Name: "docker", + Source: "github.com/hashicorp/docker", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "docker"}, + Requirement: VersionConstraint{ + Required: nil, + }, + PluginDependencyReason: PluginDependencyImplicit, + }, + }}, + }, + }, + }}, + + {"required-plugin-renamed", PackerConfig{ + parser: getBasicParser(func(p *Parser) { + p.PluginConfig.BuilderRedirects = map[string]string{ + "amazon-chroot": "github.com/hashicorp/amazon", + } + }, + )}, + ` + packer { + required_plugins { + amazon-v1 = { + source = "github.com/hashicorp/amazon" + version = "~> v1.0" + } + } + }`, ` + source "amazon-v1-chroot" "example" { + } + source "amazon-chroot" "example" { + } + `, + false, + PackerConfig{ + Packer: struct { + VersionConstraints []VersionConstraint + RequiredPlugins []*RequiredPlugins + }{ + RequiredPlugins: []*RequiredPlugins{ + {RequiredPlugins: map[string]*RequiredPlugin{ + "amazon-v1": { + Name: "amazon-v1", + Source: "github.com/hashicorp/amazon", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "amazon"}, + Requirement: VersionConstraint{ + Required: mustVersionConstraints(version.NewConstraint("~> v1.0")), + }, + PluginDependencyReason: PluginDependencyExplicit, + }, + }}, + {RequiredPlugins: map[string]*RequiredPlugin{ + "amazon": { + Name: "amazon", + Source: "github.com/hashicorp/amazon", + Type: &addrs.Plugin{Hostname: "github.com", Namespace: "hashicorp", Type: "amazon"}, + Requirement: VersionConstraint{ + Required: nil, + }, + PluginDependencyReason: PluginDependencyImplicit, + }, + }}, + }, + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := tt.cfg + file, diags := cfg.parser.ParseHCL([]byte(tt.requirePlugins), "required_plugins.pkr.hcl") + if len(diags) > 0 { + t.Fatal(diags) + } + if diags := cfg.decodeRequiredPluginsBlock(file); len(diags) > 0 { + t.Fatal(diags) + } + + rest, diags := cfg.parser.ParseHCL([]byte(tt.restOfTemplate), "rest.pkr.hcl") + if len(diags) > 0 { + t.Fatal(diags) + } + if gotDiags := cfg.decodeImplicitRequiredPluginsBlocks(rest); (len(gotDiags) > 0) != tt.wantDiags { + t.Fatal(gotDiags) + } + if diff := cmp.Diff(tt.wantConfig, cfg, cmpOpts...); diff != "" { + t.Errorf("PackerConfig.inferImplicitRequiredPluginFromBlocks() unexpected PackerConfig: %v", diff) + } + }) + } +} diff --git a/main.go b/main.go index 12ad1aca5..aa0b51d2e 100644 --- a/main.go +++ b/main.go @@ -297,6 +297,67 @@ func loadConfig() (*config, error) { PluginMinPort: 10000, PluginMaxPort: 25000, KnownPluginFolders: packer.PluginFolders("."), + + // BuilderRedirects + BuilderRedirects: map[string]string{ + + // "amazon-chroot": "github.com/hashicorp/amazon", + // "amazon-ebs": "github.com/hashicorp/amazon", + // "amazon-ebssurrogate": "github.com/hashicorp/amazon", + // "amazon-ebsvolume": "github.com/hashicorp/amazon", + // "amazon-instance": "github.com/hashicorp/amazon", + + // "azure-arm": "github.com/hashicorp/azure", + // "azure-chroot": "github.com/hashicorp/azure", + // "dtl": "github.com/hashicorp/azure", + + // "docker": "github.com/hashicorp/docker", + + // "googlecompute": "github.com/hashicorp/googlecompute", + + // "parallels-iso": "github.com/hashicorp/parallels", + // "parallels-pvm": "github.com/hashicorp/parallels", + + // "qemu": "github.com/hashicorp/qemu", + + // "vagrant": "github.com/hashicorp/vagrant", + + // "virtualbox-iso": "github.com/hashicorp/virtualbox", + // "virtualbox-ovf": "github.com/hashicorp/virtualbox", + // "virtualbox-vm": "github.com/hashicorp/virtualbox", + + // "vmware-iso": "github.com/hashicorp/vmware", + // "vmware-vmx": "github.com/hashicorp/vmware", + + // "vsphere-iso": "github.com/hashicorp/vsphere", + // "vsphere-clone": "github.com/hashicorp/vsphere", + }, + DatasourceRedirects: map[string]string{ + // "amazon-ami": "github.com/hashicorp/amazon", + }, + ProvisionerRedirects: map[string]string{ + // "ansible": "github.com/hashicorp/ansible", + // "ansible-local": "github.com/hashicorp/ansible", + + // "azure-dtlartifact": "github.com/hashicorp/azure", + }, + PostProcessorRedirects: map[string]string{ + // "amazon-import": "github.com/hashicorp/amazon", + + // "docker-import": "github.com/hashicorp/docker", + // "docker-push": "github.com/hashicorp/docker", + // "docker-save": "github.com/hashicorp/docker", + // "docker-tag": "github.com/hashicorp/docker", + + // "googlecompute-export": "github.com/hashicorp/googlecompute", + // "googlecompute-import": "github.com/hashicorp/googlecompute", + + // "vagrant": "github.com/hashicorp/vagrant", + // "vagrant-cloud": "github.com/hashicorp/vagrant", + + // "vsphere": "github.com/hashicorp/vsphere", + // "vsphere-template": "github.com/hashicorp/vsphere", + }, } if err := config.Plugins.Discover(); err != nil { return nil, err diff --git a/packer/plugin-getter/plugins.go b/packer/plugin-getter/plugins.go index 8355afd87..e996a9d0c 100644 --- a/packer/plugin-getter/plugins.go +++ b/packer/plugin-getter/plugins.go @@ -38,6 +38,9 @@ type Requirement struct { // VersionConstraints as defined by user. Empty ( to be avoided ) means // highest found version. VersionConstraints version.Constraints + + // was this require implicitly guessed ? + Implicit bool } type BinaryInstallationOptions struct { diff --git a/packer/plugin.go b/packer/plugin.go index 9235398c9..c5e1b56b1 100644 --- a/packer/plugin.go +++ b/packer/plugin.go @@ -24,6 +24,23 @@ type PluginConfig struct { Provisioners ProvisionerSet PostProcessors PostProcessorSet DataSources DatasourceSet + + // Redirects are only set when a plugin was completely moved out; they allow + // telling where a plugin has moved by checking if a known component of this + // plugin is used. For example implicitly require the + // github.com/hashicorp/amazon plugin if it was moved out and the + // "amazon-ebs" plugin is used, but not found. + // + // Redirects will be bypassed if the redirected components are already found + // in their corresponding sets (Builders, Provisioners, PostProcessors, + // DataSources). That is, for example, if you manually put a single + // component plugin in the plugins folder. + // + // Example BuilderRedirects: "amazon-ebs" => "github.com/hashicorp/amazon" + BuilderRedirects map[string]string + DatasourceRedirects map[string]string + ProvisionerRedirects map[string]string + PostProcessorRedirects map[string]string } // PACKERSPACE is used to represent the spaces that separate args for a command diff --git a/website/content/docs/commands/init.mdx b/website/content/docs/commands/init.mdx index e4c3dbad1..ce165081d 100644 --- a/website/content/docs/commands/init.mdx +++ b/website/content/docs/commands/init.mdx @@ -67,10 +67,21 @@ plugins will be installed in the [Plugin Directory](/docs/configure#packer-s-plugin-directory). See [Installing Plugins](/docs/plugins#installing-plugins) for more information on how plugin installation works. + +### Implicit required plugin + +This is part of a set of breaking changes made to decouple Packer releases from +plugin releases. To make the transition easier, we will tag components of these +plugins as "moved out". If one of the components of a moved out plugin is used +in a config file, but there is no mention of that plugin in the +"required_plugin" block, then Packer init will automatically download and +install that plugin. Packer will then display a warning and suggest that you +add the plugin to your required_plugin block. We recommend you use the +required_plugin block even if you are only using official plugins, because it +allows you to set the plugin version to avoid surprises in the future. + ## Options - `-upgrade` - On top of installing missing plugins, update installed plugins to the latest available version, if there is a new higher one. Note that this still takes into consideration the version constraint of the config. - -