add basic inferImplicitRequiredPluginFromBlocks + tests

this is not done; I think testing with an actual parsing will be better
azr_implicit_requried_plugin_2
Adrien Delorme 5 years ago
parent 3ac5046260
commit 37e494a6e8

@ -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)
}
})
}
}
Loading…
Cancel
Save