Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add path attribute to required plugins #12641

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions hcl2template/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Di
continue
}

if block.Path != "" {
continue
}

reqs = append(reqs, &plugingetter.Requirement{
Accessor: name,
Identifier: block.Type,
Expand All @@ -54,6 +58,19 @@ func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Di
return reqs, diags
}

func (cfg *PackerConfig) getLocalPlugins() []*RequiredPlugin {
reqs := []*RequiredPlugin{}
for _, block := range cfg.Packer.RequiredPlugins {
for _, reqPlugin := range block.RequiredPlugins {
if reqPlugin.Path != "" {
reqs = append(reqs, reqPlugin)
}
}
}

return reqs
}

func (cfg *PackerConfig) DetectPluginBinaries() hcl.Diagnostics {
opts := plugingetter.ListInstallationsOptions{
FromFolders: cfg.parser.PluginConfig.KnownPluginFolders,
Expand Down Expand Up @@ -106,6 +123,21 @@ func (cfg *PackerConfig) DetectPluginBinaries() hcl.Diagnostics {
}
}

localPlugins := cfg.getLocalPlugins()
for _, local := range localPlugins {
err := cfg.parser.PluginConfig.DiscoverMultiPlugin(local.Name, local.Path)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Failed to load required plugin",
Detail: fmt.Sprintf("The required plugin %q, failed to be loaded from path %q: %s",
local.Name,
local.Path,
err),
})
}
}

if len(uninstalledPlugins) > 0 {
detailMessage := &strings.Builder{}
detailMessage.WriteString("The following plugins are required, but not installed:\n\n")
Expand Down
47 changes: 43 additions & 4 deletions hcl2template/types.required_plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package hcl2template

import (
"fmt"
"os"

"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
Expand Down Expand Up @@ -51,7 +52,10 @@ type RequiredPlugin struct {
// for example, "awesomecloud" instead of github.com/awesome/awesomecloud.
// This one is left here in case we want to go back to allowing inexplicit
// source url definitions.
Source string
Source string
// Path supersedes every other attribute if specified, and will load the
// plugin from the local path specified.
Path string
Type *addrs.Plugin
Requirement VersionConstraint
DeclRange hcl.Range
Expand Down Expand Up @@ -103,6 +107,42 @@ func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnos
continue

case expr.Type().IsObjectType():
if expr.Type().HasAttribute("path") {
path := expr.GetAttr("path")

if !path.Type().Equals(cty.String) || path.IsNull() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Invalid "path" value`,
Detail: `Path must be specified as a string. For example: path = "./packer-plugin-example"`,
Subject: attr.Expr.Range().Ptr(),
})
break
}

rp.Path = path.AsString()

_, err := os.Stat(rp.Path)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Plugin not found",
Detail: fmt.Sprintf("The plugin %q could not be found at path %q: %s", attr.Name, rp.Path, err),
Subject: attr.Expr.Range().Ptr(),
})
}

diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Loading plugin from local path",
Detail: fmt.Sprintf("The plugin %q is loaded from a local path %q. "+
"This reduces the portability of the template, and thus should"+
" only be used for local testing.", attr.Name, rp.Path),
})

break
}

if !expr.Type().HasAttribute("version") {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Expand Down Expand Up @@ -182,18 +222,17 @@ func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnos

attrTypes := expr.Type().AttributeTypes()
for name := range attrTypes {
if name == "version" || name == "source" {
if name == "version" || name == "source" || name == "path" {
continue
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid required_plugins object",
Detail: `required_plugins objects can only contain "version" and "source" attributes.`,
Detail: `required_plugins objects can only contain "version", "source" or "path" attributes.`,
Subject: attr.Expr.Range().Ptr(),
})
break
}

default:
// should not happen
diags = append(diags, &hcl.Diagnostic{
Expand Down
45 changes: 44 additions & 1 deletion hcl2template/types.required_plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,47 @@ func TestPackerConfig_required_plugin_parse(t *testing.T) {
RequiredPlugins: nil,
},
}},
{
name: "test-path-nonexistent-plugin",
cfg: PackerConfig{
parser: getBasicParser(func(p *Parser) {}),
},
requirePlugins: `
packer {
required_plugins {
unknown = {
path = "./invalid"
}
}
}
`,
restOfTemplate: `
source "null" "test" {
communicator = "none"
}
build {
sources = ["null.test"]
}
`,
wantDiags: true,
wantConfig: PackerConfig{
Packer: struct {
VersionConstraints []VersionConstraint
RequiredPlugins []*RequiredPlugins
}{
RequiredPlugins: []*RequiredPlugins{
{
RequiredPlugins: map[string]*RequiredPlugin{
"unknown": {
Name: "unknown",
Path: "./invalid",
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -138,7 +179,9 @@ func TestPackerConfig_required_plugin_parse(t *testing.T) {
if len(diags) > 0 {
t.Fatal(diags)
}
if diags := cfg.decodeRequiredPluginsBlock(file); len(diags) > 0 {

diags = cfg.decodeRequiredPluginsBlock(file)
if !tt.wantDiags && len(diags) > 0 {
t.Fatal(diags)
}

Expand Down