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,
|
||||
})
|
||||
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
|
||||
|
|
|
@ -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{},
|
||||
|
|
|
@ -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,
|
||||
Identifier: block.Type,
|
||||
VersionConstraints: block.Requirement.Required,
|
||||
Implicit: block.PluginDependencyReason == PluginDependencyImplicit,
|
||||
})
|
||||
uniq[name] = block
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue