mirror of
https://github.com/hashicorp/packer.git
synced 2026-09-27 02:04:01 -04:00
add basic inferImplicitRequiredPluginFromBlocks + tests
this is not done; I think testing with an actual parsing will be better
This commit is contained in:
@@ -2,6 +2,7 @@ package hcl2template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
@@ -45,6 +46,9 @@ func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Di
|
||||
// is 'implicitly used'. Here we read common configuration blocks to try to
|
||||
// guess plugins.
|
||||
|
||||
// decodeRequiredPluginsBlock needs to be called first; otherwise all
|
||||
// required plugins will be implicitly required too.
|
||||
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
content, moreDiags := f.Body.Content(configSchema)
|
||||
@@ -52,13 +56,96 @@ func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Di
|
||||
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
case sourceLabel:
|
||||
// TODO
|
||||
case sourceLabel, dataSourceLabel:
|
||||
moreDiags := cfg.inferImplicitRequiredPluginFromBlocks(block)
|
||||
diags = append(diags, moreDiags...)
|
||||
case buildLabel:
|
||||
content, moreDiags := block.Body.Content(buildSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
case buildProvisionerLabel, buildPostProcessorLabel:
|
||||
moreDiags := cfg.inferImplicitRequiredPluginFromBlocks(block)
|
||||
diags = append(diags, moreDiags...)
|
||||
case buildPostProcessorsLabel:
|
||||
content, moreDiags := block.Body.Content(postProcessorsSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
for _, block := range content.Blocks {
|
||||
moreDiags := cfg.inferImplicitRequiredPluginFromBlocks(block)
|
||||
diags = append(diags, moreDiags...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
// This function will infer an implicitly required plugin block. For plugins
|
||||
// that are not present. Usually the plugin name is the first part of a plugin
|
||||
// call, before the first dash.
|
||||
//
|
||||
// Exampes:
|
||||
// * data "amazon-ami" "..." adds implictly requried "github.com/hashicorp/amazon"
|
||||
// * source "amazon-ebs" "..." adds implictly requried "github.com/hashicorp/amazon"
|
||||
// * source "google" "..." adds implictly requried "github.com/hashicorp/google"
|
||||
// * provisioner "windos-restart" "..." adds implictly requried "github.com/hashicorp/windows"
|
||||
// * post-processor "exoscale-import" "..." adds implictly requried "github.com/hashicorp/exoscale"
|
||||
// * source "amazon-v2-ebs" "..." adds implictly requried "github.com/hashicorp/exoscale"
|
||||
//
|
||||
// For now this function will only work with hashicorp plugins, and therefore
|
||||
// pretend the user meant using an official hashicorp plugin if the plugin could
|
||||
// not be found.
|
||||
//
|
||||
// Plugin name will stop at first dash found, so that means that if a users uses
|
||||
// an "amazon-v2" plugin, this won't work.
|
||||
func (cfg *PackerConfig) inferImplicitRequiredPluginFromBlocks(block *hcl.Block) hcl.Diagnostics {
|
||||
labels := block.Labels
|
||||
if len(labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
var diags hcl.Diagnostics
|
||||
probablePluginNames := strings.Split(labels[0], "-")
|
||||
if len(probablePluginNames) == 0 || probablePluginNames[0] == "" {
|
||||
// probably a WIP config, return now to avoid panics.
|
||||
return nil
|
||||
}
|
||||
probablePluginName := probablePluginNames[0]
|
||||
|
||||
for _, requiredPluginBlock := range cfg.Packer.RequiredPlugins {
|
||||
for _, requiredPlugin := range requiredPluginBlock.RequiredPlugins {
|
||||
if requiredPlugin.Name == probablePluginName {
|
||||
// Found a plugin that matches this name. No implicitly required
|
||||
// plugin needed.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
fullImportPath := "github.com/hashicorp/" + probablePluginName
|
||||
pType, diags := addrs.ParsePluginSourceString(fullImportPath)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
cfg.Packer.RequiredPlugins = append(cfg.Packer.RequiredPlugins, &RequiredPlugins{
|
||||
RequiredPlugins: map[string]*RequiredPlugin{
|
||||
probablePluginName: {
|
||||
Name: probablePluginName,
|
||||
Source: fullImportPath,
|
||||
Type: pType,
|
||||
Requirement: VersionConstraint{
|
||||
Required: nil, // means latest
|
||||
},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
},
|
||||
DeclRange: block.DefRange,
|
||||
})
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// RequiredPlugin represents a declaration of a dependency on a particular
|
||||
// Plugin version or source.
|
||||
type RequiredPlugin struct {
|
||||
@@ -71,8 +158,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,187 @@
|
||||
package hcl2template
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/packer/hcl2template/addrs"
|
||||
)
|
||||
|
||||
func TestPackerConfig_decodeImplicitRequiredPluginsBlocks(t *testing.T) {
|
||||
type fields struct {
|
||||
PackerConfig
|
||||
}
|
||||
type args struct {
|
||||
block *hcl.Block
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantDiags bool
|
||||
wantConfig PackerConfig
|
||||
}{
|
||||
{"invalid block", fields{PackerConfig: PackerConfig{}}, args{block: &hcl.Block{}}, false, PackerConfig{}},
|
||||
{"invalid block name", fields{PackerConfig: PackerConfig{}}, args{block: &hcl.Block{Labels: []string{""}}}, false, PackerConfig{}},
|
||||
{"implicitly require amazon plugin through datasource",
|
||||
fields{PackerConfig: PackerConfig{}},
|
||||
args{block: &hcl.Block{Labels: []string{"amazon-ami"}}},
|
||||
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{"github.com", "hashicorp", "amazon"},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"don't replace explicitly imported amazon plugin",
|
||||
fields{PackerConfig: PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{
|
||||
RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"amazon": {
|
||||
Name: "amazon",
|
||||
Source: "github.com/hashicorp/amazon",
|
||||
Type: &addrs.Plugin{"github.com", "hashicorp", "amazon"},
|
||||
PluginDependencyReason: PluginDependencyExplicit,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
args{block: &hcl.Block{Labels: []string{"amazon-ami"}}},
|
||||
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{"github.com", "hashicorp", "amazon"},
|
||||
PluginDependencyReason: PluginDependencyExplicit,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"implict import of a plugin without a dash",
|
||||
fields{PackerConfig: PackerConfig{}},
|
||||
args{block: &hcl.Block{Labels: []string{"google"}}},
|
||||
false,
|
||||
PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{
|
||||
RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"google": {
|
||||
Name: "google",
|
||||
Source: "github.com/hashicorp/google",
|
||||
Type: &addrs.Plugin{"github.com", "hashicorp", "google"},
|
||||
PluginDependencyReason: PluginDependencyImplicit,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
{"ignore already imported google plugin",
|
||||
fields{PackerConfig: PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{
|
||||
RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"google": {
|
||||
Name: "google",
|
||||
Source: "github.com/hashicorp/google",
|
||||
Type: &addrs.Plugin{"github.com", "hashicorp", "google"},
|
||||
PluginDependencyReason: PluginDependencyExplicit,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
args{block: &hcl.Block{Labels: []string{"google"}}},
|
||||
false,
|
||||
PackerConfig{
|
||||
Packer: struct {
|
||||
VersionConstraints []VersionConstraint
|
||||
RequiredPlugins []*RequiredPlugins
|
||||
}{
|
||||
RequiredPlugins: []*RequiredPlugins{
|
||||
{
|
||||
RequiredPlugins: map[string]*RequiredPlugin{
|
||||
"google": {
|
||||
Name: "google",
|
||||
Source: "github.com/hashicorp/google",
|
||||
Type: &addrs.Plugin{"github.com", "hashicorp", "google"},
|
||||
PluginDependencyReason: PluginDependencyExplicit,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := PackerConfig{
|
||||
Packer: tt.fields.Packer,
|
||||
Basedir: tt.fields.Basedir,
|
||||
CorePackerVersionString: tt.fields.CorePackerVersionString,
|
||||
Cwd: tt.fields.Cwd,
|
||||
Sources: tt.fields.Sources,
|
||||
InputVariables: tt.fields.InputVariables,
|
||||
LocalVariables: tt.fields.LocalVariables,
|
||||
Datasources: tt.fields.Datasources,
|
||||
LocalBlocks: tt.fields.LocalBlocks,
|
||||
ValidationOptions: tt.fields.ValidationOptions,
|
||||
Builds: tt.fields.Builds,
|
||||
parser: tt.fields.parser,
|
||||
files: tt.fields.files,
|
||||
except: tt.fields.except,
|
||||
only: tt.fields.only,
|
||||
force: tt.fields.force,
|
||||
debug: tt.fields.debug,
|
||||
onError: tt.fields.onError,
|
||||
}
|
||||
if gotDiags := cfg.inferImplicitRequiredPluginFromBlocks(tt.args.block); (len(gotDiags) > 0) != tt.wantDiags {
|
||||
t.Errorf("PackerConfig.inferImplicitRequiredPluginFromBlocks() = %v", gotDiags)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantConfig, cfg, cmpopts.IgnoreUnexported(PackerConfig{})); diff != "" {
|
||||
t.Errorf("PackerConfig.inferImplicitRequiredPluginFromBlocks() unexpected PackerConfig: %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user