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
This commit is contained in:
parent
70ceed1110
commit
0e3fcb589b
|
@ -121,12 +121,54 @@ func (c *InitCommand) RunContext(buildCtx context.Context, cla *InitArgs) int {
|
||||||
Getters: getters,
|
Getters: getters,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
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())
|
c.Ui.Error(err.Error())
|
||||||
ret = 1
|
ret = 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if newInstall != nil {
|
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)
|
msg := fmt.Sprintf("Installed plugin %s %s in %q", pluginRequirement.Identifier, newInstall.Version, newInstall.BinaryPath)
|
||||||
ui.Say(msg)
|
ui.Say(msg)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -20,8 +20,8 @@ import (
|
||||||
|
|
||||||
const lockedVersion = "v1.5.0"
|
const lockedVersion = "v1.5.0"
|
||||||
|
|
||||||
func getBasicParser() *Parser {
|
func getBasicParser(opts ...getParserOption) *Parser {
|
||||||
return &Parser{
|
parser := &Parser{
|
||||||
CorePackerVersion: version.Must(version.NewSemver(lockedVersion)),
|
CorePackerVersion: version.Must(version.NewSemver(lockedVersion)),
|
||||||
CorePackerVersionString: lockedVersion,
|
CorePackerVersionString: lockedVersion,
|
||||||
Parser: hclparse.NewParser(),
|
Parser: hclparse.NewParser(),
|
||||||
|
@ -44,7 +44,13 @@ func getBasicParser() *Parser {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
for _, configure := range opts {
|
||||||
|
configure(parser)
|
||||||
}
|
}
|
||||||
|
return parser
|
||||||
|
}
|
||||||
|
|
||||||
|
type getParserOption func(*Parser)
|
||||||
|
|
||||||
type parseTestArgs struct {
|
type parseTestArgs struct {
|
||||||
filename string
|
filename string
|
||||||
|
@ -338,7 +344,7 @@ var cmpOpts = []cmp.Option{
|
||||||
PackerConfig{},
|
PackerConfig{},
|
||||||
Variable{},
|
Variable{},
|
||||||
SourceBlock{},
|
SourceBlock{},
|
||||||
Datasource{},
|
DatasourceBlock{},
|
||||||
ProvisionerBlock{},
|
ProvisionerBlock{},
|
||||||
PostProcessorBlock{},
|
PostProcessorBlock{},
|
||||||
packer.CoreBuild{},
|
packer.CoreBuild{},
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -41,6 +41,7 @@ func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Di
|
||||||
Accessor: name,
|
Accessor: name,
|
||||||
Identifier: block.Type,
|
Identifier: block.Type,
|
||||||
VersionConstraints: block.Requirement.Required,
|
VersionConstraints: block.Requirement.Required,
|
||||||
|
Implicit: block.PluginDependencyReason == PluginDependencyImplicit,
|
||||||
})
|
})
|
||||||
uniq[name] = block
|
uniq[name] = block
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DataBlock references an HCL 'data' block.
|
// DatasourceBlock references an HCL 'data' block.
|
||||||
type Datasource struct {
|
type DatasourceBlock struct {
|
||||||
Type string
|
Type string
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
@ -25,9 +25,9 @@ type DatasourceRef struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Datasources map[DatasourceRef]Datasource
|
type Datasources map[DatasourceRef]DatasourceBlock
|
||||||
|
|
||||||
func (data *Datasource) Ref() DatasourceRef {
|
func (data *DatasourceBlock) Ref() DatasourceRef {
|
||||||
return DatasourceRef{
|
return DatasourceRef{
|
||||||
Type: data.Type,
|
Type: data.Type,
|
||||||
Name: data.Name,
|
Name: data.Name,
|
||||||
|
@ -124,9 +124,9 @@ func (cfg *PackerConfig) startDatasource(dataSourceStore packer.DatasourceStore,
|
||||||
return datasource, diags
|
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
|
var diags hcl.Diagnostics
|
||||||
r := &Datasource{
|
r := &DatasourceBlock{
|
||||||
Type: block.Labels[0],
|
Type: block.Labels[0],
|
||||||
Name: block.Labels[1],
|
Name: block.Labels[1],
|
||||||
block: block,
|
block: block,
|
||||||
|
|
|
@ -131,7 +131,7 @@ func TestParser_complete(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Datasources: Datasources{
|
Datasources: Datasources{
|
||||||
DatasourceRef{Type: "amazon-ami", Name: "test"}: Datasource{
|
DatasourceRef{Type: "amazon-ami", Name: "test"}: DatasourceBlock{
|
||||||
Type: "amazon-ami",
|
Type: "amazon-ami",
|
||||||
Name: "test",
|
Name: "test",
|
||||||
value: cty.StringVal("foo"),
|
value: cty.StringVal("foo"),
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/packer/hcl2template/addrs"
|
"github.com/hashicorp/packer/hcl2template/addrs"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"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 {
|
func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Diagnostics {
|
||||||
// when a plugin is used but not defined in the required plugin blocks, it
|
// when a plugin is used but not available it should be 'implicitly
|
||||||
// is 'implicitly used'. Here we read common configuration blocks to try to
|
// required'. Here we read common configuration blocks to try to guess
|
||||||
// guess plugins.
|
// plugin usages.
|
||||||
|
|
||||||
|
// decodeRequiredPluginsBlock needs to be called before
|
||||||
|
// decodeImplicitRequiredPluginsBlocks; otherwise all required plugins will
|
||||||
|
// be implicitly required too.
|
||||||
|
|
||||||
var diags hcl.Diagnostics
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
|
@ -51,14 +56,112 @@ func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Di
|
||||||
diags = append(diags, moreDiags...)
|
diags = append(diags, moreDiags...)
|
||||||
|
|
||||||
for _, block := range content.Blocks {
|
for _, block := range content.Blocks {
|
||||||
|
|
||||||
switch block.Type {
|
switch block.Type {
|
||||||
case sourceLabel:
|
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
|
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
|
// RequiredPlugin represents a declaration of a dependency on a particular
|
||||||
// Plugin version or source.
|
// Plugin version or source.
|
||||||
type RequiredPlugin struct {
|
type RequiredPlugin struct {
|
||||||
|
@ -71,8 +174,24 @@ type RequiredPlugin struct {
|
||||||
Type *addrs.Plugin
|
Type *addrs.Plugin
|
||||||
Requirement VersionConstraint
|
Requirement VersionConstraint
|
||||||
DeclRange hcl.Range
|
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 {
|
type RequiredPlugins struct {
|
||||||
RequiredPlugins map[string]*RequiredPlugin
|
RequiredPlugins map[string]*RequiredPlugin
|
||||||
DeclRange hcl.Range
|
DeclRange hcl.Range
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
61
main.go
61
main.go
|
@ -297,6 +297,67 @@ func loadConfig() (*config, error) {
|
||||||
PluginMinPort: 10000,
|
PluginMinPort: 10000,
|
||||||
PluginMaxPort: 25000,
|
PluginMaxPort: 25000,
|
||||||
KnownPluginFolders: packer.PluginFolders("."),
|
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 {
|
if err := config.Plugins.Discover(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -38,6 +38,9 @@ type Requirement struct {
|
||||||
// VersionConstraints as defined by user. Empty ( to be avoided ) means
|
// VersionConstraints as defined by user. Empty ( to be avoided ) means
|
||||||
// highest found version.
|
// highest found version.
|
||||||
VersionConstraints version.Constraints
|
VersionConstraints version.Constraints
|
||||||
|
|
||||||
|
// was this require implicitly guessed ?
|
||||||
|
Implicit bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type BinaryInstallationOptions struct {
|
type BinaryInstallationOptions struct {
|
||||||
|
|
|
@ -24,6 +24,23 @@ type PluginConfig struct {
|
||||||
Provisioners ProvisionerSet
|
Provisioners ProvisionerSet
|
||||||
PostProcessors PostProcessorSet
|
PostProcessors PostProcessorSet
|
||||||
DataSources DatasourceSet
|
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
|
// PACKERSPACE is used to represent the spaces that separate args for a command
|
||||||
|
|
|
@ -67,10 +67,21 @@ plugins will be installed in the [Plugin
|
||||||
Directory](/docs/configure#packer-s-plugin-directory).
|
Directory](/docs/configure#packer-s-plugin-directory).
|
||||||
|
|
||||||
See [Installing Plugins](/docs/plugins#installing-plugins) for more information on how plugin installation works.
|
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
|
## Options
|
||||||
|
|
||||||
- `-upgrade` - On top of installing missing plugins, update installed plugins to
|
- `-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
|
the latest available version, if there is a new higher one. Note that this
|
||||||
still takes into consideration the version constraint of the config.
|
still takes into consideration the version constraint of the config.
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue