Merge pull request #10633 from hashicorp/azr_init_no_magic_host

Packer init: remove host and namespace guessing
This commit is contained in:
Megan Marsh 2021-02-16 14:32:20 -08:00 committed by GitHub
commit 1710590418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 279 additions and 156 deletions

View File

@ -2,7 +2,7 @@
### FEATURES ### FEATURES
** New Command** (HCL only) `packer init` command will download plugins defined ** New Command** (HCL only) `packer init` command will download plugins defined
in a new `required_plugins` block [GH-10304] in a new `required_plugins` block [GH-10304] [GH-10633].
** New Plugin Type** Data sources can be implemented (blog post forthcoming). ** New Plugin Type** Data sources can be implemented (blog post forthcoming).
[GH-10440] [GH-10440]
** New Plugin** Aws Secrets Manager data source [GH-10505] [GH-10467] ** New Plugin** Aws Secrets Manager data source [GH-10505] [GH-10467]

View File

@ -109,7 +109,7 @@ func (c *InitCommand) RunContext(buildCtx context.Context, cla *InitArgs) int {
return 1 return 1
} }
log.Printf("[TRACE] for plugin %s found %d matching installation(s)", pluginRequirement.Identifier.ForDisplay(), len(installs)) log.Printf("[TRACE] for plugin %s found %d matching installation(s)", pluginRequirement.Identifier, len(installs))
if len(installs) > 0 && cla.Upgrade == false { if len(installs) > 0 && cla.Upgrade == false {
continue continue
@ -125,7 +125,7 @@ func (c *InitCommand) RunContext(buildCtx context.Context, cla *InitArgs) int {
ret = 1 ret = 1
} }
if newInstall != nil { if newInstall != nil {
msg := fmt.Sprintf("Installed plugin %s %s in %q", pluginRequirement.Identifier.ForDisplay(), 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)
} }
} }

View File

@ -291,6 +291,32 @@ func TestInitCommand_Run(t *testing.T) {
testBuild{want: 1}.fn, testBuild{want: 1}.fn,
}, },
}, },
{
"unsupported-non-github-source-address",
[]func(t *testing.T, tc testCaseInit){
skipInitTestUnlessEnVar(acctest.TestEnvVar).fn,
},
testMetaFile(t),
nil,
"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
map[string]string{
`cfg.pkr.hcl`: `
packer {
required_plugins {
comment = {
source = "example.com/sylviamoss/comment"
version = "v0.2.19"
}
}
}`,
},
cfg.dir("6_pkr_config"),
cfg.dir("6_pkr_user_folder"),
1,
nil,
"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
nil,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -10,17 +10,13 @@ import (
// Plugin encapsulates a single plugin type. // Plugin encapsulates a single plugin type.
type Plugin struct { type Plugin struct {
Type string
Namespace string
Hostname string Hostname string
Namespace string
Type string
} }
func (p Plugin) RealRelativePath() string { func (p Plugin) RealRelativePath() string {
ns := DefaultPluginNamespace return p.Namespace + "/packer-plugin-" + p.Type
if p.Namespace != "" {
ns = p.Namespace
}
return ns + "/packer-plugin-" + p.Type
} }
func (p Plugin) Parts() []string { func (p Plugin) Parts() []string {
@ -31,23 +27,6 @@ func (p Plugin) String() string {
return strings.Join(p.Parts(), "/") return strings.Join(p.Parts(), "/")
} }
// ForDisplay returns a user-friendly FQN string, simplified for readability. If
// the plugin is using the default hostname, the hostname is omitted.
func (p *Plugin) ForDisplay() string {
parts := []string{}
if p.Hostname != DefaultPluginHost {
parts = append(parts, p.Hostname)
}
if p.Namespace != DefaultPluginNamespace {
parts = append(parts, p.Namespace)
}
parts = append(parts, p.Type)
return strings.Join(parts, "/")
}
const DefaultPluginHost = "github.com"
const DefaultPluginNamespace = "hashicorp"
// ParsePluginPart processes an addrs.Plugin namespace or type string // ParsePluginPart processes an addrs.Plugin namespace or type string
// provided by an end-user, producing a normalized version if possible or // provided by an end-user, producing a normalized version if possible or
// an error if the string contains invalid characters. // an error if the string contains invalid characters.
@ -120,18 +99,18 @@ func IsPluginPartNormalized(str string) (bool, error) {
// hostname/namespace/name // hostname/namespace/name
func ParsePluginSourceString(str string) (*Plugin, hcl.Diagnostics) { func ParsePluginSourceString(str string) (*Plugin, hcl.Diagnostics) {
ret := &Plugin{ ret := &Plugin{
Hostname: DefaultPluginHost, Hostname: "",
Namespace: DefaultPluginNamespace, Namespace: "",
} }
var diags hcl.Diagnostics var diags hcl.Diagnostics
// split the source string into individual components // split the source string into individual components
parts := strings.Split(str, "/") parts := strings.Split(str, "/")
if len(parts) == 0 || len(parts) > 3 { if len(parts) != 3 {
diags = diags.Append(&hcl.Diagnostic{ diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid plugin source string", Summary: "Invalid plugin source string",
Detail: `The "source" attribute must be in the format "[hostname/][namespace/]name"`, Detail: `The "source" attribute must be in the format "hostname/namespace/name"`,
}) })
return nil, diags return nil, diags
} }
@ -142,7 +121,7 @@ func ParsePluginSourceString(str string) (*Plugin, hcl.Diagnostics) {
diags = diags.Append(&hcl.Diagnostic{ diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid plugin source string", Summary: "Invalid plugin source string",
Detail: `The "source" attribute must be in the format "[hostname/][namespace/]name"`, Detail: `The "source" attribute must be in the format "hostname/namespace/name"`,
}) })
return nil, diags return nil, diags
} }
@ -161,33 +140,21 @@ func ParsePluginSourceString(str string) (*Plugin, hcl.Diagnostics) {
} }
ret.Type = name ret.Type = name
if len(parts) == 1 { // the namespace is always the second-to-last part
return ret, diags givenNamespace := parts[len(parts)-2]
namespace, err := ParsePluginPart(givenNamespace)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin namespace",
Detail: fmt.Sprintf(`Invalid plugin namespace %q in source %q: %s"`, namespace, str, err),
})
return nil, diags
} }
ret.Namespace = namespace
if len(parts) >= 2 { // the hostname is always the first part in a three-part source string
// the namespace is always the second-to-last part ret.Hostname = parts[0]
givenNamespace := parts[len(parts)-2]
namespace, err := ParsePluginPart(givenNamespace)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin namespace",
Detail: fmt.Sprintf(`Invalid plugin namespace %q in source %q: %s"`, namespace, str, err),
})
return nil, diags
}
ret.Namespace = namespace
}
// Final Case: 3 parts
if len(parts) == 3 {
// the hostname is always the first part in a three-part source string
hostname := parts[0]
// TODO(azr): validate host ? Can this be something else than a
// github.com host for now?
ret.Hostname = hostname
}
// Due to how plugin executables are named and plugin git repositories // Due to how plugin executables are named and plugin git repositories
// are conventionally named, it's a reasonable and // are conventionally named, it's a reasonable and
@ -217,7 +184,10 @@ func ParsePluginSourceString(str string) (*Plugin, hcl.Diagnostics) {
diags = diags.Append(&hcl.Diagnostic{ diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid plugin type", Summary: "Invalid plugin type",
Detail: fmt.Sprintf("Plugin source %q has a type with the prefix %q, which isn't valid. Although that prefix is often used in the names of version control repositories for Packer plugins, plugin source strings should not include it.\n\nDid you mean %q?", ret.ForDisplay(), userErrorPrefix, suggestedAddr.ForDisplay()), Detail: fmt.Sprintf("Plugin source %q has a type with the prefix %q, which isn't valid. "+
"Although that prefix is often used in the names of version control repositories for Packer plugins, "+
"plugin source strings should not include it.\n"+
"\nDid you mean %q?", ret, userErrorPrefix, suggestedAddr),
}) })
return nil, diags return nil, diags
} }

View File

@ -0,0 +1,32 @@
package addrs
import (
"reflect"
"testing"
)
func TestParsePluginSourceString(t *testing.T) {
type args struct {
str string
}
tests := []struct {
args args
want *Plugin
wantDiags bool
}{
{args{"potato"}, nil, true},
{args{"hashicorp/azr"}, nil, true},
{args{"github.com/hashicorp/azr"}, &Plugin{"github.com", "hashicorp", "azr"}, false},
}
for _, tt := range tests {
t.Run(tt.args.str, func(t *testing.T) {
got, gotDiags := ParsePluginSourceString(tt.args.str)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParsePluginSourceString() got = %v, want %v", got, tt.want)
}
if tt.wantDiags == (len(gotDiags) == 0) {
t.Errorf("Unexpected diags %s", gotDiags)
}
})
}
}

View File

@ -156,7 +156,9 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
// equivalent of having : // equivalent of having :
// packer { // packer {
// required_plugins { // required_plugins {
// amazon = "latest" // amazon = {
// version = "latest"
// source = "github.com/hashicorp/amazon"
// } // }
// } // }
// Note: using `latest` ( or actually an empty string ) in a config file // Note: using `latest` ( or actually an empty string ) in a config file

View File

@ -78,7 +78,7 @@ func (cfg *PackerConfig) detectPluginBinaries() hcl.Diagnostics {
if err != nil { if err != nil {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to list installation for %s", pluginRequirement.Identifier.ForDisplay()), Summary: fmt.Sprintf("Failed to list installation for %s", pluginRequirement.Identifier),
Detail: err.Error(), Detail: err.Error(),
}) })
continue continue
@ -86,18 +86,18 @@ func (cfg *PackerConfig) detectPluginBinaries() hcl.Diagnostics {
if len(sortedInstalls) == 0 { if len(sortedInstalls) == 0 {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: fmt.Sprintf("no plugin installed for %s %v", pluginRequirement.Identifier.ForDisplay(), pluginRequirement.VersionConstraints.String()), Summary: fmt.Sprintf("no plugin installed for %s %v", pluginRequirement.Identifier, pluginRequirement.VersionConstraints.String()),
Detail: "Did you run packer init for this project ?", Detail: "Did you run packer init for this project ?",
}) })
continue continue
} }
log.Printf("[TRACE] Found the following %q installations: %v", pluginRequirement.Identifier.ForDisplay(), sortedInstalls) log.Printf("[TRACE] Found the following %q installations: %v", pluginRequirement.Identifier, sortedInstalls)
install := sortedInstalls[len(sortedInstalls)-1] install := sortedInstalls[len(sortedInstalls)-1]
err = cfg.parser.PluginConfig.DiscoverMultiPlugin(pluginRequirement.Accessor, install.BinaryPath) err = cfg.parser.PluginConfig.DiscoverMultiPlugin(pluginRequirement.Accessor, install.BinaryPath)
if err != nil { if err != nil {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: fmt.Sprintf("Error discovering plugin %s", pluginRequirement.Identifier.ForDisplay()), Summary: fmt.Sprintf("Error discovering plugin %s", pluginRequirement.Identifier),
Detail: err.Error(), Detail: err.Error(),
}) })
continue continue

View File

@ -1,7 +1,13 @@
packer { packer {
required_plugins { required_plugins {
amazon = ">= v0" amazon = {
amazon = ">= v4" source = "github.com/hashicorp/amazon"
version = ">= v0"
}
amazon = {
source = "github.com/hashicorp/amazon"
version = ">= v4"
}
} }
} }

View File

@ -3,29 +3,26 @@ packer {
required_version = ">= v1" required_version = ">= v1"
required_plugins { required_plugins {
amazon = ">= v0" amazon = {
source = "github.com/hashicorp/amazon"
version = ">= v0"
}
amazon-v1 = { amazon-v1 = {
source = "amazon" source = "github.com/hashicorp/amazon"
version = ">= v1" version = ">= v1"
} }
amazon-v2 = { amazon-v2 = {
source = "amazon" source = "github.com/hashicorp/amazon"
version = ">= v2" version = ">= v2"
} }
amazon-v3 = { amazon-v3 = {
source = "hashicorp/amazon" source = "github.com/hashicorp/amazon"
version = ">= v3" version = ">= v3"
} }
amazon-v3-azr = { amazon-v3-azr = {
source = "azr/amazon" source = "github.com/azr/amazon"
version = ">= v3" version = ">= v3"
} }
amazon-v4 = { amazon-v4 = {
source = "github.com/hashicorp/amazon" source = "github.com/hashicorp/amazon"
version = ">= v4" version = ">= v4"

View File

@ -0,0 +1,8 @@
packer {
required_plugins {
amazon = {
source = "amazon"
version = ">= v0"
}
}
}

View File

@ -0,0 +1,8 @@
packer {
required_plugins {
amazon = {
source = "hashicorp/amazon"
version = ">= v0"
}
}
}

View File

@ -0,0 +1,5 @@
packer {
required_plugins {
amazon = ">= v0"
}
}

View File

@ -1,6 +1,7 @@
package hcl2template package hcl2template
import ( import (
"path/filepath"
"testing" "testing"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
@ -431,7 +432,7 @@ func TestParser_no_init(t *testing.T) {
RequiredPlugins: map[string]*RequiredPlugin{ RequiredPlugins: map[string]*RequiredPlugin{
"amazon": { "amazon": {
Name: "amazon", Name: "amazon",
Source: "", Source: "github.com/hashicorp/amazon",
Type: &addrs.Plugin{ Type: &addrs.Plugin{
Type: "amazon", Type: "amazon",
Namespace: "hashicorp", Namespace: "hashicorp",
@ -443,7 +444,7 @@ func TestParser_no_init(t *testing.T) {
}, },
"amazon-v1": { "amazon-v1": {
Name: "amazon-v1", Name: "amazon-v1",
Source: "amazon", Source: "github.com/hashicorp/amazon",
Type: &addrs.Plugin{ Type: &addrs.Plugin{
Type: "amazon", Type: "amazon",
Namespace: "hashicorp", Namespace: "hashicorp",
@ -455,7 +456,7 @@ func TestParser_no_init(t *testing.T) {
}, },
"amazon-v2": { "amazon-v2": {
Name: "amazon-v2", Name: "amazon-v2",
Source: "amazon", Source: "github.com/hashicorp/amazon",
Type: &addrs.Plugin{ Type: &addrs.Plugin{
Type: "amazon", Type: "amazon",
Namespace: "hashicorp", Namespace: "hashicorp",
@ -467,7 +468,7 @@ func TestParser_no_init(t *testing.T) {
}, },
"amazon-v3": { "amazon-v3": {
Name: "amazon-v3", Name: "amazon-v3",
Source: "hashicorp/amazon", Source: "github.com/hashicorp/amazon",
Type: &addrs.Plugin{ Type: &addrs.Plugin{
Type: "amazon", Type: "amazon",
Namespace: "hashicorp", Namespace: "hashicorp",
@ -479,7 +480,7 @@ func TestParser_no_init(t *testing.T) {
}, },
"amazon-v3-azr": { "amazon-v3-azr": {
Name: "amazon-v3-azr", Name: "amazon-v3-azr",
Source: "azr/amazon", Source: "github.com/azr/amazon",
Type: &addrs.Plugin{ Type: &addrs.Plugin{
Type: "amazon", Type: "amazon",
Namespace: "azr", Namespace: "azr",
@ -610,6 +611,66 @@ func TestParser_no_init(t *testing.T) {
[]packersdk.Build{}, []packersdk.Build{},
false, false,
}, },
{"invalid_inexplicit_source.pkr.hcl",
defaultParser,
parseTestArgs{"testdata/init/invalid_inexplicit_source.pkr.hcl", nil, nil},
&PackerConfig{
Packer: struct {
VersionConstraints []VersionConstraint
RequiredPlugins []*RequiredPlugins
}{
VersionConstraints: nil,
RequiredPlugins: []*RequiredPlugins{
{},
},
},
CorePackerVersionString: lockedVersion,
Basedir: filepath.Clean("testdata/init"),
},
true, true,
[]packersdk.Build{},
false,
},
{"invalid_short_source.pkr.hcl",
defaultParser,
parseTestArgs{"testdata/init/invalid_short_source.pkr.hcl", nil, nil},
&PackerConfig{
Packer: struct {
VersionConstraints []VersionConstraint
RequiredPlugins []*RequiredPlugins
}{
VersionConstraints: nil,
RequiredPlugins: []*RequiredPlugins{
{},
},
},
CorePackerVersionString: lockedVersion,
Basedir: filepath.Clean("testdata/init"),
},
true, true,
[]packersdk.Build{},
false,
},
{"invalid_inexplicit_source_2.pkr.hcl",
defaultParser,
parseTestArgs{"testdata/init/invalid_inexplicit_source_2.pkr.hcl", nil, nil},
&PackerConfig{
Packer: struct {
VersionConstraints []VersionConstraint
RequiredPlugins []*RequiredPlugins
}{
VersionConstraints: nil,
RequiredPlugins: []*RequiredPlugins{
{},
},
},
CorePackerVersionString: lockedVersion,
Basedir: filepath.Clean("testdata/init"),
},
true, true,
[]packersdk.Build{},
false,
},
} }
testParse_only_Parse(t, tests) testParse_only_Parse(t, tests)
} }

View File

@ -62,7 +62,11 @@ func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Di
// 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 {
Name string Name string
// Source used to be able to tell how the template referenced this source,
// 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
Type *addrs.Plugin Type *addrs.Plugin
Requirement VersionConstraint Requirement VersionConstraint
@ -77,7 +81,7 @@ type RequiredPlugins struct {
func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnostics) { func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnostics) {
attrs, diags := block.Body.JustAttributes() attrs, diags := block.Body.JustAttributes()
ret := &RequiredPlugins{ ret := &RequiredPlugins{
RequiredPlugins: make(map[string]*RequiredPlugin), RequiredPlugins: nil,
DeclRange: block.DefRange, DeclRange: block.DefRange,
} }
for name, attr := range attrs { for name, attr := range attrs {
@ -96,18 +100,24 @@ func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnos
switch { switch {
case expr.Type().IsPrimitiveType(): case expr.Type().IsPrimitiveType():
vc, reqDiags := decodeVersionConstraint(attr) c := "version"
diags = append(diags, reqDiags...) if cs, _ := decodeVersionConstraint(attr); len(cs.Required) > 0 {
rp.Requirement = vc c = cs.Required.String()
rp.Type, err = addrs.ParsePluginSourceString(name)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin type",
Detail: fmt.Sprintf(`Invalid plugin type %q: %s"`, name, err),
})
} }
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin requirement",
Detail: fmt.Sprintf(`'%s = "%s"' plugin requirement calls are not possible.`+
` You must define a whole block. For example:`+"\n"+
`%[1]s = {`+"\n"+
` source = "github.com/hashicorp/%[1]s"`+"\n"+
` version = "%[2]s"`+"\n"+`}`,
name, c),
Subject: attr.Range.Ptr(),
})
continue
case expr.Type().IsObjectType(): case expr.Type().IsObjectType():
if !expr.Type().HasAttribute("version") { if !expr.Type().HasAttribute("version") {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
@ -140,8 +150,10 @@ func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnos
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid version constraint", Summary: "Invalid version constraint",
Detail: "This string does not use correct version constraint syntax. See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#version-constraint-syntax for docs.", Detail: "This string does not use correct version constraint syntax. " +
Subject: attr.Expr.Range().Ptr(), "See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#version-constraint-syntax for docs.\n" +
err.Error(),
Subject: attr.Expr.Range().Ptr(),
}) })
continue continue
} }
@ -179,6 +191,7 @@ func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnos
} }
} }
diags = append(diags, sourceDiags...) diags = append(diags, sourceDiags...)
continue
} else { } else {
rp.Type = p rp.Type = p
} }
@ -207,6 +220,9 @@ func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnos
}) })
} }
if ret.RequiredPlugins == nil {
ret.RequiredPlugins = make(map[string]*RequiredPlugin)
}
ret.RequiredPlugins[rp.Name] = rp ret.RequiredPlugins[rp.Name] = rp
} }

View File

@ -82,7 +82,6 @@ func (v *Variable) GoString() string {
// validateValue ensures that all of the configured custom validations for a // validateValue ensures that all of the configured custom validations for a
// variable value are passing. // variable value are passing.
//
func (v *Variable) validateValue(val VariableAssignment) (diags hcl.Diagnostics) { func (v *Variable) validateValue(val VariableAssignment) (diags hcl.Diagnostics) {
if len(v.Validations) == 0 { if len(v.Validations) == 0 {
log.Printf("[TRACE] validateValue: not active for %s, so skipping", v.Name) log.Printf("[TRACE] validateValue: not active for %s, so skipping", v.Name)

View File

@ -23,6 +23,7 @@ import (
const ( const (
ghTokenAccessor = "PACKER_GITHUB_API_TOKEN" ghTokenAccessor = "PACKER_GITHUB_API_TOKEN"
defaultUserAgent = "packer-plugin-getter" defaultUserAgent = "packer-plugin-getter"
defaultHostname = "github.com"
) )
type Getter struct { type Getter struct {
@ -154,6 +155,11 @@ func (t *HostSpecificTokenAuthTransport) base() http.RoundTripper {
} }
func (g *Getter) Get(what string, opts plugingetter.GetOptions) (io.ReadCloser, error) { func (g *Getter) Get(what string, opts plugingetter.GetOptions) (io.ReadCloser, error) {
if opts.PluginRequirement.Identifier.Hostname != defaultHostname {
s := opts.PluginRequirement.Identifier.String() + " doesn't appear to be a valid " + defaultHostname + " source address; check source and try again."
return nil, errors.New(s)
}
ctx := context.TODO() ctx := context.TODO()
if g.Client == nil { if g.Client == nil {
var tc *http.Client var tc *http.Client

View File

@ -27,7 +27,7 @@ type Requirements []*Requirement
type Requirement struct { type Requirement struct {
// Plugin accessor as defined in the config file. // Plugin accessor as defined in the config file.
// For Packer, using : // For Packer, using :
// required_plugins { amazon = ">= v0" } // required_plugins { amazon = {...} }
// Will set Accessor to `amazon`. // Will set Accessor to `amazon`.
Accessor string Accessor string
@ -81,7 +81,7 @@ func (pr Requirement) ListInstallations(opts ListInstallationsOptions) (InstallL
res := InstallList{} res := InstallList{}
FilenamePrefix := pr.FilenamePrefix() FilenamePrefix := pr.FilenamePrefix()
filenameSuffix := opts.filenameSuffix() filenameSuffix := opts.filenameSuffix()
log.Printf("[TRACE] listing potential installations for %q that match %q. %#v", pr.Identifier.ForDisplay(), pr.VersionConstraints, opts) log.Printf("[TRACE] listing potential installations for %q that match %q. %#v", pr.Identifier, pr.VersionConstraints, opts)
for _, knownFolder := range opts.FromFolders { for _, knownFolder := range opts.FromFolders {
glob := filepath.Join(knownFolder, pr.Identifier.Hostname, pr.Identifier.Namespace, pr.Identifier.Type, FilenamePrefix+"*"+filenameSuffix) glob := filepath.Join(knownFolder, pr.Identifier.Hostname, pr.Identifier.Namespace, pr.Identifier.Type, FilenamePrefix+"*"+filenameSuffix)
@ -345,7 +345,7 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
getters := opts.Getters getters := opts.Getters
fail := fmt.Errorf("could not find a local nor a remote checksum for plugin %q %q", pr.Identifier, pr.VersionConstraints) fail := fmt.Errorf("could not find a local nor a remote checksum for plugin %q %q", pr.Identifier, pr.VersionConstraints)
log.Printf("[TRACE] getting available versions for the the %s plugin", pr.Identifier.ForDisplay()) log.Printf("[TRACE] getting available versions for the %s plugin", pr.Identifier)
versions := version.Collection{} versions := version.Collection{}
for _, getter := range getters { for _, getter := range getters {
@ -397,7 +397,7 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
log.Printf("[DEBUG] will try to install: %s", versions) log.Printf("[DEBUG] will try to install: %s", versions)
if len(versions) == 0 { if len(versions) == 0 {
err := fmt.Errorf("no release version found for the %s plugin matching the constraint(s): %q", pr.Identifier.ForDisplay(), pr.VersionConstraints.String()) err := fmt.Errorf("no release version found for the %s plugin matching the constraint(s): %q", pr.Identifier, pr.VersionConstraints.String())
return nil, err return nil, err
} }
@ -411,7 +411,7 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
filepath.Join(pr.Identifier.Parts()...), filepath.Join(pr.Identifier.Parts()...),
) )
log.Printf("[TRACE] fetching checksums file for the %q version of the %s plugin in %q...", version, pr.Identifier.ForDisplay(), outputFolder) log.Printf("[TRACE] fetching checksums file for the %q version of the %s plugin in %q...", version, pr.Identifier, outputFolder)
var checksum *FileChecksum var checksum *FileChecksum
for _, getter := range getters { for _, getter := range getters {
@ -428,7 +428,7 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
version: version, version: version,
}) })
if err != nil { if err != nil {
err := fmt.Errorf("could not get %s checksum file for %s version %s. Is the file present on the release and correctly named ? %s", checksummer.Type, pr.Identifier.ForDisplay(), version, err) err := fmt.Errorf("could not get %s checksum file for %s version %s. Is the file present on the release and correctly named ? %s", checksummer.Type, pr.Identifier, version, err)
log.Printf("[TRACE] %s", err.Error()) log.Printf("[TRACE] %s", err.Error())
return nil, err return nil, err
} }
@ -486,7 +486,7 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
log.Printf("[TRACE] found a pre-exising %q checksum file", potentialChecksumer.Type) log.Printf("[TRACE] found a pre-exising %q checksum file", potentialChecksumer.Type)
// if outputFile is there and matches the checksum: do nothing more. // if outputFile is there and matches the checksum: do nothing more.
if err := localChecksum.ChecksumFile(localChecksum.Expected, potentialOutputFilename); err == nil { if err := localChecksum.ChecksumFile(localChecksum.Expected, potentialOutputFilename); err == nil {
log.Printf("[INFO] %s v%s plugin is already correctly installed in %q", pr.Identifier.ForDisplay(), version, potentialOutputFilename) log.Printf("[INFO] %s v%s plugin is already correctly installed in %q", pr.Identifier, version, potentialOutputFilename)
return nil, nil return nil, nil
} }
} }
@ -519,7 +519,7 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
expectedZipFilename: expectedZipFilename, expectedZipFilename: expectedZipFilename,
}) })
if err != nil { if err != nil {
err := fmt.Errorf("could not get binary for %s version %s. Is the file present on the release and correctly named ? %s", pr.Identifier.ForDisplay(), version, err) err := fmt.Errorf("could not get binary for %s version %s. Is the file present on the release and correctly named ? %s", pr.Identifier, version, err)
log.Printf("[TRACE] %v", err) log.Printf("[TRACE] %v", err)
continue continue
} }

View File

@ -43,7 +43,7 @@ func TestPlugin_ListInstallations(t *testing.T) {
{ {
"darwin_amazon_prot_5.0", "darwin_amazon_prot_5.0",
fields{ fields{
Identifier: "amazon", Identifier: "github.com/hashicorp/amazon",
}, },
ListInstallationsOptions{ ListInstallationsOptions{
[]string{ []string{
@ -80,7 +80,7 @@ func TestPlugin_ListInstallations(t *testing.T) {
{ {
"darwin_amazon_prot_5.1", "darwin_amazon_prot_5.1",
fields{ fields{
Identifier: "amazon", Identifier: "github.com/hashicorp/amazon",
}, },
ListInstallationsOptions{ ListInstallationsOptions{
[]string{ []string{
@ -121,7 +121,7 @@ func TestPlugin_ListInstallations(t *testing.T) {
{ {
"windows_amazon", "windows_amazon",
fields{ fields{
Identifier: "amazon", Identifier: "github.com/hashicorp/amazon",
}, },
ListInstallationsOptions{ ListInstallationsOptions{
[]string{ []string{
@ -159,7 +159,7 @@ func TestPlugin_ListInstallations(t *testing.T) {
{ {
"windows_google_multifolder", "windows_google_multifolder",
fields{ fields{
Identifier: "hashicorp/google", Identifier: "github.com/hashicorp/google",
}, },
ListInstallationsOptions{ ListInstallationsOptions{
[]string{ []string{
@ -542,7 +542,7 @@ func TestRequirement_InstallLatest(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
log.Printf("starting %s test", tt.name) log.Printf("starting %s test", tt.name)
identifier, diags := addrs.ParsePluginSourceString(tt.fields.Identifier) identifier, diags := addrs.ParsePluginSourceString("github.com/hashicorp/" + tt.fields.Identifier)
if len(diags) != 0 { if len(diags) != 0 {
t.Fatalf("ParsePluginSourceString(%q): %v", tt.fields.Identifier, diags) t.Fatalf("ParsePluginSourceString(%q): %v", tt.fields.Identifier, diags)
} }

View File

@ -44,11 +44,10 @@ block :
```hcl ```hcl
packer { packer {
required_plugins { required_plugins {
myawesomecloud = { happycloud = {
version = ">= 2.7.0" version = ">= 2.7.0"
source = "azr/myawesomecloud" source = "github.com/azr/happycloud"
} }
happycloud = ">= 2.7.0"
} }
} }
``` ```
@ -57,8 +56,8 @@ HashiCorp does not officially verify third party Packer plugins, plugins not und
## Plugin Selection ## Plugin Selection
Plugin selection depends on the source and version constraints defined within the `required_plugins` block. Plugin selection depends on the source and version constraints defined within the `required_plugins` block.
For each of the required plugins Packer will query the source repository `azr/myawesomecloud` whose fully qualified address For each of the required plugins Packer will query the source repository `github.com/azr/happycloud` whose fully qualified address
is `https://github.com/azr/packer-plugin-myawesomecloud` for a plugin matching the version constraints for the host operating system. is `https://github.com/azr/packer-plugin-happycloud` for a plugin matching the version constraints for the host operating system.
Packer init will install the latest found version matching the version selection Packer init will install the latest found version matching the version selection
in the `required_plugins` section. Make sure to set a correct [version in the `required_plugins` section. Make sure to set a correct [version

View File

@ -76,11 +76,11 @@ Here is an example `required_plugins` block:
required_plugins { required_plugins {
myawesomecloud = { myawesomecloud = {
version = ">= 2.7.0" version = ">= 2.7.0"
source = "azr/myawesomecloud" source = "github.com/azr/myawesomecloud"
} }
happycloud = { happycloud = {
version = ">= 1.1.3" version = ">= 1.1.3"
source = "azr/happycloud" source = "github.com/azr/happycloud"
} }
} }
} }
@ -130,7 +130,7 @@ If we change the required_plugins block to use a different local name "foo":
required_plugins { required_plugins {
foo = { foo = {
version = ">= 2.7.0" version = ">= 2.7.0"
source = "azr/myawesomecloud" source = "github.com/azr/myawesomecloud"
} }
} }
``` ```
@ -151,37 +151,29 @@ to download it.
Source addresses consist of three parts delimited by slashes (`/`), as Source addresses consist of three parts delimited by slashes (`/`), as
follows: follows:
`[<HOSTNAME>/]<NAMESPACE>/<TYPE>` `<HOSTNAME>/<NAMESPACE>/<TYPE>`
* **Hostname** (optional): The hostname of the location/service that * **Hostname:** The hostname of the location/service that
distributes the plugin. If omitted, this defaults to distributes the plugin. Currently, the only valid "hostname" is github.com,
`github.com`, we recommend explicitly setting the hostname. Currently, the but we plan to eventually support plugins downloaded from other domains.
only valid "hostname" is github.com, but we plan to eventually support plugins
downloaded from other domains.
* **Namespace:** An organizational namespace within the specified host. * **Namespace:** An organizational namespace within the specified host.
This often is the organization that publishes the plugin. If omitted, this This often is the organization that publishes the plugin.
defaults to `hashicorp`. We recommend explicitly setting the namespace.
* **Type:** A short name for the platform or system the plugin manages. The * **Type:** A short name for the platform or system the plugin manages. The
type is usually the plugin's preferred local name. type is usually the plugin's preferred local name.
For example, the fictional `myawesomecloud` plugin could belong to the For example, the fictional `myawesomecloud` plugin could belong to the
`hashicorp` namespace on `github.com`, so its `source` could be `hashicorp` namespace on `github.com`, so its `source` could be
`github.com/hashicorp/myawesomecloud`, `hashicorp/myawesomecloud` or `github.com/hashicorp/myawesomecloud`,
`myawesomecloud`. Note: the actual _repository_ that myawesomecloud comes from Note: the actual _repository_ that myawesomecloud comes from must always have
must always have the name format the name format `github.com/hashicorp/packer-plugin-myawesomecloud`, but the
`www.github.com/hashicorp/packer-plugin-myawesomecloud`, but the
`required_plugins` block omits the redundant `packer-plugin-` repository prefix `required_plugins` block omits the redundant `packer-plugin-` repository prefix
for brevity. for brevity.
The source address with all three components given explicitly is called the The source address with all three components given explicitly is called the
plugin's _fully-qualified address_. You will see fully-qualified address in plugin's _fully-qualified address_. You will see fully-qualified address in
various outputs, like error messages, but in most cases a simplified display various outputs, like error messages.
version is used. Therefore you may see the shortened version `"myawesomecloud"`
instead of `"github.com/hashicorp/myawesomecloud"`.
-> **Note:** We recommend using explicit source addresses for all plugins.
## Plugin location ## Plugin location
@ -192,17 +184,15 @@ instead of `"github.com/hashicorp/myawesomecloud"`.
Using the following example : Using the following example :
```hcl ```hcl
required_plugins { required_plugins {
myawesomecloud = { happycloud = {
version = ">= 2.7.0" version = ">= 2.7.0"
source = "azr/myawesomecloud" source = "github.com/azr/happycloud"
} }
happycloud = ">= 2.7.0"
} }
``` ```
The plugin getter will look for plugins located at: The plugin getter will look for plugins located at:
* github.com/azr/packer-plugin-myawesomecloud * github.com/azr/packer-plugin-happycloud
* github.com/hashicorp/packer-plugin-happycloud
Packer will error if you set the `packer-plugin-` prefix in a `source`. This Packer will error if you set the `packer-plugin-` prefix in a `source`. This
will avoid conflicting with other plugins for other tools, like Terraform. will avoid conflicting with other plugins for other tools, like Terraform.

View File

@ -59,11 +59,10 @@ version constraint.
```hcl ```hcl
packer { packer {
required_plugins { required_plugins {
myawesomecloud = { happycloud = {
version = ">= 2.7.0" version = ">= 2.7.0"
source = "hashicorp/myawesomecloud" source = "github.com/hashicorp/happycloud"
} }
happycloud = ">= 2.7.0"
} }
} }
``` ```

View File

@ -135,10 +135,12 @@ in the rest of the template.
Here it is a brief explanation of each field: Here it is a brief explanation of each field:
- `version` - Should follow the [version constraints](/docs/templates/hcl_templates/blocks/packer#version-constraints). - `version` - Should follow the [version constraints](/docs/templates/hcl_templates/blocks/packer#version-constraints).
- `source` - Should have the plugin's organizational namespace on GitHub and its short name. Packer will be responsible - `source` - Should have the GitHub hostname, the plugin's organizational namespace, and its short name.
for determining the full GitHub address. Packer will be responsible for determining the full GitHub
For example, if the source is `sylviamoss/comment`, Packer will download the binaries from `github.com/sylviamoss/packer-plugin-comment`. address. For example, if the source is `github.com/sylviamoss/comment`, Packer
To learn more about the source field, check out the [Source Address](/docs/plugins#source-addresses) documentation. will download the binaries from `github.com/sylviamoss/packer-plugin-comment`.
To learn more about the source field, check out the [Source
Address](/docs/plugins#source-addresses) documentation.
- `local_name`- Can be replaced with whatever you want, and the new value will become the name of the plugin. - `local_name`- Can be replaced with whatever you want, and the new value will become the name of the plugin.
For example: For example:
```hcl ```hcl
@ -180,4 +182,3 @@ and won't need to run `init` again unless you want to upgrade the plugin version
A template with a `required_plugins` block should **always** be initialised at least once with `packer init` before A template with a `required_plugins` block should **always** be initialised at least once with `packer init` before
`packer build`. If the template is built before init, Packer will fail and ask for initialisation. `packer build`. If the template is built before init, Packer will fail and ask for initialisation.

View File

@ -24,28 +24,26 @@ colon (`:`) on other systems. The order priority will be kept.
Using the following example : Using the following example :
```hcl ```hcl
required_plugins { required_plugins {
myawesomecloud = { happycloud = {
version = ">= 2.7.0" version = ">= 2.7.0"
source = "azr/myawesomecloud" source = "github.com/azr/happycloud"
} }
happycloud = ">= 2.7.0"
} }
``` ```
The plugin getter will then install the binaries in the following location for a The plugin getter will then install the binaries in the following location for a
system with no `PACKER_PLUGIN_PATH` env var set. system with no `PACKER_PLUGIN_PATH` env var set.
* `PACKER_HOME_DIR/plugins/github.com/azr/myawesomecloud/`
* `PACKER_HOME_DIR/plugins/github.com/hashicorp/happycloud/` * `PACKER_HOME_DIR/plugins/github.com/hashicorp/happycloud/`
During initialization, on a `darwin_amd64` system, Packer will look-up for the During initialization, on a `darwin_amd64` system, Packer will look-up for the
following files: following files:
* `PACKER_EXEC_DIR/github.com/azr/myawesomecloud/packer-plugin-myawesomecloud_*_darwin_amd64_x5` * `PACKER_EXEC_DIR/github.com/azr/happycloud/packer-plugin-happycloud_*_darwin_amd64_x5.0`
* `./github.com/azr/myawesomecloud/packer-plugin-myawesomecloud_*_darwin_amd64_x5` * `./github.com/azr/happycloud/packer-plugin-happycloud_*_darwin_amd64_x5.0`
* `PACKER_HOME_DIR/plugins/github.com/azr/myawesomecloud/packer-plugin-myawesomecloud_*_darwin_amd64_x5` * `PACKER_HOME_DIR/plugins/github.com/azr/happycloud/packer-plugin-happycloud_*_darwin_amd64_x5.0`
* `PACKER_PLUGIN_PATH/github.com/azr/myawesomecloud/packer-plugin-myawesomecloud_*_darwin_amd64_x5` * `PACKER_PLUGIN_PATH/github.com/azr/happycloud/packer-plugin-happycloud_*_darwin_amd64_x5.0`
* `./packer-plugin-myawesomecloud` * `./packer-plugin-happycloud`
The first plugin-name/version files found will take precedence. The first plugin-name/version files found will take precedence.